/*
 * Copyright 2018 LinkedIn Corp. Licensed under the BSD 2-Clause License (the "License"). See License in the project root for license information.
 */

package com.linkedin.kafka.cruisecontrol.servlet.response;

import com.google.gson.Gson;
import com.linkedin.kafka.cruisecontrol.config.TopicConfigProvider;
import com.linkedin.kafka.cruisecontrol.config.KafkaCruiseControlConfig;
import com.linkedin.cruisecontrol.servlet.parameters.CruiseControlParameters;
import com.linkedin.kafka.cruisecontrol.servlet.parameters.KafkaClusterStateParameters;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import org.apache.kafka.common.Cluster;

import static com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils.JSON_VERSION;
import static com.linkedin.kafka.cruisecontrol.servlet.response.ResponseUtils.VERSION;

@JsonResponseClass
public class KafkaClusterState extends AbstractCruiseControlResponse {
  @JsonResponseField
  protected static final String KAFKA_BROKER_STATE = "KafkaBrokerState";
  @JsonResponseField
  protected static final String KAFKA_PARTITION_STATE = "KafkaPartitionState";
  protected final Map<String, Properties> _allTopicConfigs;
  protected final Properties _clusterConfigs;
  protected final Map<String, Object> _adminClientConfigs;
  protected Cluster _kafkaCluster;

  public KafkaClusterState(Cluster kafkaCluster,
                           TopicConfigProvider topicConfigProvider,
                           Map<String, Object> adminClientConfigs,
                           KafkaCruiseControlConfig config) {
    super(config);
    _kafkaCluster = kafkaCluster;
    _allTopicConfigs = topicConfigProvider.allTopicConfigs();
    _clusterConfigs = topicConfigProvider.clusterConfigs();
    _adminClientConfigs = adminClientConfigs;
  }

  protected String getJSONString(CruiseControlParameters parameters) {
    Gson gson = new Gson();
    Map<String, Object> jsonStructure;
    KafkaClusterStateParameters kafkaClusterStateParams = (KafkaClusterStateParameters) parameters;
    boolean isVerbose = kafkaClusterStateParams.isVerbose();
    Pattern topic = kafkaClusterStateParams.topic();
    try {
      jsonStructure = getJsonStructure(isVerbose, topic);
      jsonStructure.put(VERSION, JSON_VERSION);
    }  catch (InterruptedException | ExecutionException e) {
      throw new RuntimeException("Failed to populate broker logDir state.", e);
    }
    return gson.toJson(jsonStructure);
  }

  /**
   * Return an object that can be further used to encode into JSON.
   *
   * @param verbose True if verbose, false otherwise.
   * @param topic Regex of topic to filter partition states by, is null if no filter is to be applied
   * @return An object that can be further used to encode into JSON.
   */
  protected Map<String, Object> getJsonStructure(boolean verbose, Pattern topic)
      throws ExecutionException, InterruptedException {
    Map<String, Object> cruiseControlState = new HashMap<>();
    cruiseControlState.put(KAFKA_BROKER_STATE,
                           new ClusterBrokerState(_kafkaCluster, _adminClientConfigs, _config).getJsonStructure());
    cruiseControlState.put(KAFKA_PARTITION_STATE,
                           new ClusterPartitionState(verbose, topic, _kafkaCluster, _allTopicConfigs, _clusterConfigs).getJsonStructure());
    return cruiseControlState;
  }

  protected String getPlaintext(CruiseControlParameters parameters) {
    KafkaClusterStateParameters kafkaClusterStateParams = (KafkaClusterStateParameters) parameters;
    boolean isVerbose = kafkaClusterStateParams.isVerbose();
    Pattern topic = kafkaClusterStateParams.topic();
    StringBuilder sb = new StringBuilder();
    try {
      // Broker summary.
      new ClusterBrokerState(_kafkaCluster, _adminClientConfigs, _config).writeBrokerSummary(sb);
      // Partition summary.
      new ClusterPartitionState(isVerbose, topic, _kafkaCluster, _allTopicConfigs, _clusterConfigs).writePartitionSummary(sb, isVerbose);
    } catch (InterruptedException | ExecutionException e) {
      throw new RuntimeException("Failed to populate broker logDir state.", e);
    }
    return sb.toString();
  }

  @Override
  protected void discardIrrelevantAndCacheRelevant(CruiseControlParameters parameters) {
    // Cache relevant response.
    _cachedResponse = parameters.json() ? getJSONString(parameters) : getPlaintext(parameters);
    // Discard irrelevant response.
    _kafkaCluster = null;
  }
}