/*
 * Copyright DataStax, Inc.
 *
 * Licensed 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 java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
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>
 * <li>all hosts that have been tried have failed.</li>
 * </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<InetSocketAddress, Throwable> errors;

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

    private NoHostAvailableException(String message, Throwable cause, Map<InetSocketAddress, 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<InetSocketAddress, Throwable> getErrors() {
        return new HashMap<InetSocketAddress, 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<InetSocketAddress, 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<InetSocketAddress, 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();
    }
}
