/*
 * 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 com.datastax.driver.core.exceptions;

import com.datastax.driver.core.EndPoint;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * Exception thrown when a query cannot be performed because no host is available.
 *
 * <p>This exception is thrown if either:
 *
 * <ul>
 *   <li>there is no host live in the cluster at the moment of the query;
 *   <li>all hosts that have been tried have failed.
 * </ul>
 *
 * <p>For debugging purposes, the list of hosts that have been tried along with the failure cause
 * can be retrieved using the {@link #getErrors()} method.
 */
public class NoHostAvailableException extends DriverException {

  private static final long serialVersionUID = 0;

  private static final int MAX_ERRORS_IN_DEFAULT_MESSAGE = 3;

  private final Map<EndPoint, Throwable> errors;

  public NoHostAvailableException(Map<EndPoint, Throwable> errors) {
    super(makeMessage(errors, MAX_ERRORS_IN_DEFAULT_MESSAGE, false, false));
    this.errors = errors;
  }

  private NoHostAvailableException(
      String message, Throwable cause, Map<EndPoint, Throwable> errors) {
    super(message, cause);
    this.errors = errors;
  }

  /**
   * Return the hosts tried along with the error encountered while trying them.
   *
   * @return a map containing for each tried host the error triggered when trying it.
   */
  public Map<EndPoint, Throwable> getErrors() {
    return new HashMap<EndPoint, Throwable>(errors);
  }

  /**
   * Builds a custom message for this exception.
   *
   * @param maxErrors the maximum number of errors displayed (useful to limit the size of the
   *     message for big clusters). Beyond this limit, host names are still displayed, but not the
   *     associated errors. Set to {@code Integer.MAX_VALUE} to display all hosts.
   * @param formatted whether to format the output (line break between each host).
   * @param includeStackTraces whether to include the full stacktrace of each host error. Note that
   *     this automatically implies {@code formatted}.
   * @return the message.
   */
  public String getCustomMessage(int maxErrors, boolean formatted, boolean includeStackTraces) {
    if (includeStackTraces) formatted = true;
    return makeMessage(errors, maxErrors, formatted, includeStackTraces);
  }

  @Override
  public NoHostAvailableException copy() {
    return new NoHostAvailableException(getMessage(), this, errors);
  }

  private static String makeMessage(
      Map<EndPoint, Throwable> errors,
      int maxErrorsInMessage,
      boolean formatted,
      boolean includeStackTraces) {
    if (errors.size() == 0) return "All host(s) tried for query failed (no host was tried)";

    StringWriter stringWriter = new StringWriter();
    PrintWriter out = new PrintWriter(stringWriter);

    out.print("All host(s) tried for query failed (tried:");
    out.print(formatted ? "\n" : " ");

    int n = 0;
    boolean truncated = false;
    for (Map.Entry<EndPoint, Throwable> entry : errors.entrySet()) {
      if (n > 0) out.print(formatted ? "\n" : ", ");
      out.print(entry.getKey());
      if (n < maxErrorsInMessage) {
        if (includeStackTraces) {
          out.print("\n");
          entry.getValue().printStackTrace(out);
          out.print("\n");
        } else {
          out.printf(" (%s)", entry.getValue());
        }
      } else {
        truncated = true;
      }
      n += 1;
    }
    if (truncated) {
      out.print(formatted ? "\n" : " ");
      out.printf(
          "[only showing errors of first %d hosts, use getErrors() for more details]",
          maxErrorsInMessage);
    }
    if (formatted && !includeStackTraces) out.print("\n");
    out.print(")");
    out.close();
    return stringWriter.toString();
  }
}
