/*
 * This file is part of Bisq.
 *
 * Bisq is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bisq is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
 */

package bisq.cli;

import bisq.proto.grpc.GetBalanceGrpc;
import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionRequest;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import joptsimple.OptionException;
import joptsimple.OptionParser;

import java.text.DecimalFormat;

import java.io.IOException;
import java.io.PrintStream;

import java.math.BigDecimal;

import java.util.List;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

import static java.lang.System.err;
import static java.lang.System.exit;
import static java.lang.System.out;

/**
 * A command-line client for the Bisq gRPC API.
 */
@Slf4j
public class CliMain {

    private static final int EXIT_SUCCESS = 0;
    private static final int EXIT_FAILURE = 1;

    private enum Command {
        getversion,
        getbalance
    }

    public static void main(String[] args) {
        var parser = new OptionParser();

        var helpOpt = parser.accepts("help", "Print this help text")
                .forHelp();

        var hostOpt = parser.accepts("host", "Bisq node hostname or IP")
                .withRequiredArg()
                .defaultsTo("localhost");

        var portOpt = parser.accepts("port", "Bisq node rpc port")
                .withRequiredArg()
                .ofType(Integer.class)
                .defaultsTo(9998);

        var passwordOpt = parser.accepts("password", "Bisq node rpc server password")
                .withRequiredArg();

        try {
            var options = parser.parse(args);

            if (options.has(helpOpt)) {
                printHelp(parser, out);
                exit(EXIT_SUCCESS);
            }

            @SuppressWarnings("unchecked")
            var nonOptionArgs = (List<String>) options.nonOptionArguments();
            if (nonOptionArgs.isEmpty()) {
                printHelp(parser, err);
                err.println("Error: no command specified");
                exit(EXIT_FAILURE);
            }

            var commandName = nonOptionArgs.get(0);
            Command command = null;
            try {
                command = Command.valueOf(commandName);
            } catch (IllegalArgumentException ex) {
                err.printf("Error: '%s' is not a supported command\n", commandName);
                exit(EXIT_FAILURE);
            }

            var host = options.valueOf(hostOpt);
            var port = options.valueOf(portOpt);
            var password = options.valueOf(passwordOpt);
            if (password == null) {
                err.println("Error: rpc server password not specified");
                exit(EXIT_FAILURE);
            }

            var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
            var credentials = new AuthHeaderCallCredentials(password);

            switch (command) {
                case getversion: {
                    var stub = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
                    var request = GetVersionRequest.newBuilder().build();
                    var version = stub.getVersion(request).getVersion();
                    out.println(version);
                    shutdown(channel);
                    exit(EXIT_SUCCESS);
                }
                case getbalance: {
                    var stub = GetBalanceGrpc.newBlockingStub(channel).withCallCredentials(credentials);
                    var request = GetBalanceRequest.newBuilder().build();
                    var balance = stub.getBalance(request).getBalance();
                    if (balance == -1) {
                        err.println("Error: server is still initializing");
                        shutdown(channel);
                        exit(EXIT_FAILURE);
                    }
                    out.println(formatBalance(balance));
                    shutdown(channel);
                    exit(EXIT_SUCCESS);
                }
                default: {
                    err.printf("Error: unhandled command '%s'\n", command);
                    exit(EXIT_FAILURE);
                }
            }
        } catch (OptionException ex) {
            err.println("Error: " + ex.getMessage());
            exit(EXIT_FAILURE);
        } catch (StatusRuntimeException ex) {
            Throwable t = ex.getCause() == null ? ex : ex.getCause();
            err.println("Error: " + t.getMessage());
            exit(EXIT_FAILURE);
        }
    }

    private static void printHelp(OptionParser parser, PrintStream stream) {
        try {
            stream.println("Bisq RPC Client");
            stream.println();
            stream.println("Usage: bisq-cli [options] <command>");
            stream.println();
            parser.printHelpOn(stream);
            stream.println();
            stream.println("Command           Descripiton");
            stream.println("-------           -----------");
            stream.println("getversion        Get Bisq node version");
            stream.println("getbalance        Get Bisq node wallet balance");
            stream.println();
        } catch (IOException ex) {
            ex.printStackTrace(stream);
        }
    }

    @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
    private static String formatBalance(long satoshis) {
        var btcFormat = new DecimalFormat("###,##0.00000000");
        var satoshiDivisor = new BigDecimal(100000000);
        return btcFormat.format(BigDecimal.valueOf(satoshis).divide(satoshiDivisor));
    }

    private static void shutdown(ManagedChannel channel) {
        try {
            channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

@@ -1560,22 +1560,21 @@
 te enum 
-Comman
+Metho
 d %7B%0A    
@@ -2685,22 +2685,21 @@
 ror: no 
-comman
+metho
 d specif
@@ -2772,22 +2772,21 @@
     var 
-comman
+metho
 dName = 
@@ -2823,22 +2823,20 @@
     
-Command comman
+Method metho
 d = 
@@ -2879,24 +2879,22 @@
     
-command = Comman
+method = Metho
 d.va
@@ -2899,22 +2899,21 @@
 valueOf(
-comman
+metho
 dName);%0A
@@ -3027,26 +3027,24 @@
 ted 
-command%5Cn%22, comman
+method%5Cn%22, metho
 dNam
@@ -3600,22 +3600,21 @@
 switch (
-comman
+metho
 d) %7B%0A   
@@ -4779,22 +4779,21 @@
 handled 
-comman
+metho
 d '%25s'%5Cn
@@ -4795,22 +4795,21 @@
 %25s'%5Cn%22, 
-comman
+metho
 d);%0A    
@@ -5442,22 +5442,21 @@
 tions%5D %3C
-comman
+metho
 d%3E%22);%0A  
@@ -5585,15 +5585,15 @@
 ln(%22
-Command
+Method 
     

Use 'method' vs 'command' naming