/*
 * 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.cassandra.tools.nodetool;

import java.io.PrintStream;
import java.util.Set;

import com.google.common.annotations.VisibleForTesting;

import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.MessagingServiceMBean;
import org.apache.cassandra.streaming.ProgressInfo;
import org.apache.cassandra.streaming.SessionInfo;
import org.apache.cassandra.streaming.StreamState;
import org.apache.cassandra.tools.NodeProbe;

import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

@Command(name = "netstats", description = "Print network information on provided host (connecting node by default)")
public class NetStats extends AbstractCommand
{
    @Option(paramLabel = "human_readable",
            names = { "-H", "--human-readable" },
            description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB")
    private boolean humanReadable = false;

    @Mixin
    private PrintPortMixin printPortMixin = new PrintPortMixin();

    @Override
    public void execute(NodeProbe probe)
    {
        PrintStream out = probe.output().out;
        out.printf("Mode: %s%n", probe.getOperationMode());
        Set<StreamState> statuses = probe.getStreamStatus();
        if (statuses.isEmpty())
            out.println("Not sending any streams.");
        for (StreamState status : statuses)
        {
            out.printf("%s %s%n", status.streamOperation.getDescription(), status.planId.toString());
            for (SessionInfo info : status.sessions)
            {
                out.printf("    %s", InetAddressAndPort.toString(info.peer, printPortMixin.printPort));
                // print private IP when it is used
                if (!info.peer.equals(info.connecting))
                {
                    out.printf(" (using %s)", InetAddressAndPort.toString(info.connecting, printPortMixin.printPort));
                }
                out.printf("%n");
                if (!info.receivingSummaries.isEmpty())
                {
                    printReceivingSummaries(out, info, humanReadable);
                }
                if (!info.sendingSummaries.isEmpty())
                {
                    printSendingSummaries(out, info, humanReadable);
                }
            }
        }

        if (!probe.isStarting())
        {
            out.printf("Read Repair Statistics:%nAttempted: %d%nMismatch (Blocking): %d%nMismatch (Background): %d%n", probe.getReadRepairAttempted(), probe.getReadRepairRepairedBlocking(), probe.getReadRepairRepairedBackground());

            MessagingServiceMBean ms = probe.getMessagingServiceProxy();
            out.printf("%-25s", "Pool Name");
            out.printf("%10s", "Active");
            out.printf("%10s", "Pending");
            out.printf("%15s", "Completed");
            out.printf("%10s%n", "Dropped");

            int pending;
            long completed;
            long dropped;

            pending = 0;
            for (int n : ms.getLargeMessagePendingTasksWithPort().values())
                pending += n;
            completed = 0;
            for (long n : ms.getLargeMessageCompletedTasksWithPort().values())
                completed += n;
            dropped = 0;
            for (long n : ms.getLargeMessageDroppedTasksWithPort().values())
                dropped += n;
            out.printf("%-25s%10s%10s%15s%10s%n", "Large messages", "n/a", pending, completed, dropped);

            pending = 0;
            for (int n : ms.getSmallMessagePendingTasksWithPort().values())
                pending += n;
            completed = 0;
            for (long n : ms.getSmallMessageCompletedTasksWithPort().values())
                completed += n;
            dropped = 0;
            for (long n : ms.getSmallMessageDroppedTasksWithPort().values())
                dropped += n;
            out.printf("%-25s%10s%10s%15s%10s%n", "Small messages", "n/a", pending, completed, dropped);

            pending = 0;
            for (int n : ms.getGossipMessagePendingTasksWithPort().values())
                pending += n;
            completed = 0;
            for (long n : ms.getGossipMessageCompletedTasksWithPort().values())
                completed += n;
            dropped = 0;
            for (long n : ms.getGossipMessageDroppedTasksWithPort().values())
                dropped += n;
            out.printf("%-25s%10s%10s%15s%10s%n", "Gossip messages", "n/a", pending, completed, dropped);
        }
    }

    @VisibleForTesting
    public void printReceivingSummaries(PrintStream out, SessionInfo info, boolean printHumanReadable)
    {
        long totalFilesToReceive = info.getTotalFilesToReceive();
        long totalBytesToReceive = info.getTotalSizeToReceive();
        long totalFilesReceived = info.getTotalFilesReceived();
        long totalSizeReceived = info.getTotalSizeReceived();
        double percentageFilesReceived = ((double) totalFilesReceived / totalFilesToReceive) * 100;
        double percentageSizesReceived = ((double) totalSizeReceived / totalBytesToReceive) * 100;

        out.printf("        Receiving %d files, %s total. Already received %d files (%.2f%%), %s total (%.2f%%)%n",
                   totalFilesToReceive,
                   printHumanReadable ? FileUtils.stringifyFileSize(totalBytesToReceive) : Long.toString(totalBytesToReceive) + " bytes",
                   totalFilesReceived,
                   percentageFilesReceived,
                   printHumanReadable ? FileUtils.stringifyFileSize(totalSizeReceived) : Long.toString(totalSizeReceived) + " bytes",
                   percentageSizesReceived);

        for (ProgressInfo progress : info.getReceivingFiles())
        {
            out.printf("            %s%n", progress.toString(printPortMixin.printPort));
        }
    }

    @VisibleForTesting
    public void printSendingSummaries(PrintStream out, SessionInfo info, boolean printHumanReadable)
    {
        long totalFilesToSend = info.getTotalFilesToSend();
        long totalSizeToSend = info.getTotalSizeToSend();
        long totalFilesSent = info.getTotalFilesSent();
        long totalSizeSent = info.getTotalSizeSent();
        double percentageFilesSent = ((double) totalFilesSent / totalFilesToSend) * 100;
        double percentageSizeSent = ((double) totalSizeSent / totalSizeToSend) * 100;

        out.printf("        Sending %d files, %s total. Already sent %d files (%.2f%%), %s total (%.2f%%)%n",
                   totalFilesToSend,
                   printHumanReadable ? FileUtils.stringifyFileSize(totalSizeToSend) : Long.toString(totalSizeToSend) + " bytes",
                   totalFilesSent,
                   percentageFilesSent,
                   printHumanReadable ? FileUtils.stringifyFileSize(totalSizeSent) : Long.toString(totalSizeSent) + " bytes",
                   percentageSizeSent);

        for (ProgressInfo progress : info.getSendingFiles())
        {
            out.printf("            %s%n", progress.toString(printPortMixin.printPort));
        }
    }
}
