/*
 * Copyright (c) 2020 Fran├žois Onimus
 *
 * 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.github.fonimus.ssh.shell.complete;

import com.github.fonimus.ssh.shell.SimpleTable;
import com.github.fonimus.ssh.shell.SshShellHelper;
import com.github.fonimus.ssh.shell.auth.SshAuthentication;
import com.github.fonimus.ssh.shell.commands.SshShellComponent;
import com.github.fonimus.ssh.shell.interactive.Interactive;
import com.github.fonimus.ssh.shell.interactive.KeyBinding;
import com.github.fonimus.ssh.shell.providers.AnyOsFileValueProvider;
import org.apache.sshd.server.session.ServerSession;
import org.jline.terminal.Size;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.shell.Availability;
import org.springframework.shell.CompletionContext;
import org.springframework.shell.CompletionProposal;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellMethodAvailability;
import org.springframework.shell.standard.ShellOption;
import org.springframework.shell.standard.ValueProviderSupport;
import org.springframework.stereotype.Component;

import java.io.File;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Demo command for example
 */
@SshShellComponent
public class DemoCommand {

    private static final Logger LOGGER = LoggerFactory.getLogger(DemoCommand.class);

    private final SshShellHelper helper;

    public DemoCommand(SshShellHelper helper) {
        this.helper = helper;
    }

    /**
     * Echo command
     *
     * @param message message to print
     * @return message
     */
    @ShellMethod("Echo command")
    public String echo(@ShellOption(valueProvider = CustomValuesProvider.class) String message) {
        return message;
    }

    /**
     * Terminal size command example
     *
     * @return size
     */
    @ShellMethod("Terminal size command")
    public Size size() {
        return helper.terminalSize();
    }

    /**
     * Progress displays command example
     *
     * @param progress current percentage
     */
    @ShellMethod("Progress command")
    public void progress(int progress) {
        helper.printSuccess(progress + "%");
        helper.print(helper.progress(progress));
    }

    /**
     * File provider command example
     *
     * @param file  file to get info from
     * @param anyOs any os file to get info from
     */
    @ShellMethod("File command")
    public void file(
            @ShellOption(defaultValue = ShellOption.NULL) File file,
            @ShellOption(valueProvider = AnyOsFileValueProvider.class, defaultValue = ShellOption.NULL) File anyOs) {

        info(file);
        info(anyOs);
    }

    private void info(File file) {
        if (file != null) {
            if (file.exists()) {
                helper.printSuccess("File exists: " + file.getAbsolutePath());
                helper.print("\nType: " + (file.isDirectory() ? "directory" : "file"));
                helper.print("Size: " + file.length());
            } else {
                helper.printError("File does not exist: " + file.getAbsolutePath());
            }
        }
    }

    /**
     * Interactive command example
     *
     * @param fullscreen fullscreen mode
     * @param delay      delay in ms
     */
    @ShellMethod("Interactive command")
    public void interactive(boolean fullscreen, @ShellOption(defaultValue = "3000") long delay) {

        KeyBinding binding = KeyBinding.builder()
                .description("K binding example")
                .key("k").input(() -> LOGGER.info("In specific action triggered by key 'k' !")).build();

        Interactive interactive = Interactive.builder().input((size, currentDelay) -> {
            LOGGER.info("In interactive command for input...");
            List<AttributedString> lines = new ArrayList<>();
            AttributedStringBuilder sb = new AttributedStringBuilder(size.getColumns());

            sb.append("\nCurrent time", AttributedStyle.BOLD).append(" : ");
            sb.append(String.format("%8tT", new Date()));

            lines.add(sb.toAttributedString());

            SecureRandom sr = new SecureRandom();
            lines.add(new AttributedStringBuilder().append(helper.progress(sr.nextInt(100)),
                    AttributedStyle.DEFAULT.foreground(sr.nextInt(6) + 1)).toAttributedString());
            lines.add(AttributedString.fromAnsi(SshShellHelper.INTERACTIVE_LONG_MESSAGE + "\n"));

            return lines;
        }).binding(binding).fullScreen(fullscreen).refreshDelay(delay).build();

        helper.interactive(interactive);
    }

    /**
     * Ex command
     *
     * @throws IllegalStateException for example
     */
    @ShellMethod("Ex command")
    public void ex() {
        throw new IllegalStateException("Test exception message");
    }

    /**
     * Interaction example command
     *
     * @return welcome message
     */
    @ShellMethod("Welcome command")
    public String welcome() {
        helper.printInfo("You are now in the welcome command");
        String name = helper.read("What's your name ?");
        return "Hello, '" + name + "' !";
    }

    /**
     * Confirmation example command
     *
     * @return welcome message
     */
    @ShellMethod("Confirmation command")
    public String conf() {
        return helper.confirm("Are you sure ?") ? "Great ! Let's do it !" : "Such a shame ...";
    }

    /**
     * Admin only example command
     *
     * @return welcome message
     */
    @ShellMethod("Admin command")
    @ShellMethodAvailability("adminAvailability")
    public String admin() {
        return "Finally an administrator !!";
    }

    /**
     * Check admin availability
     *
     * @return is admin
     */
    public Availability adminAvailability() {
        if (!helper.checkAuthorities(Collections.singletonList("ADMIN"))) {
            return Availability.unavailable("admin command is only for an admin users !");
        }
        return Availability.available();
    }

    /**
     * Authentication example command
     *
     * @return principal
     */
    @ShellMethod("Authentication command")
    public SshAuthentication authentication() {
        return helper.getAuthentication();
    }

    /**
     * Displays ssh session information
     */
    @ShellMethod("Displays ssh session information")
    public String displaySshSession() {
        ServerSession session = helper.getSshSession();
        if (session == null) {
            return helper.getWarning("Not in a ssh session");
        }

        return helper.renderTable(SimpleTable.builder()
                .column("Property").column("Value")
                .line(Arrays.asList("Session id", session.getIoSession().getId()))
                .line(Arrays.asList("Local address", session.getIoSession().getLocalAddress()))
                .line(Arrays.asList("Remote address", session.getIoSession().getRemoteAddress()))
                .line(Arrays.asList("Acceptance address", session.getIoSession().getAcceptanceAddress()))
                .line(Arrays.asList("Server version", session.getServerVersion()))
                .line(Arrays.asList("Client version", session.getClientVersion()))
                .build());
    }

    /**
     * For scheduled command example
     */
    @Scheduled(initialDelay = 0, fixedDelay = 60000)
    public void log() {
        LOGGER.info("In scheduled task..");
    }
}

@Component
class CustomValuesProvider
        extends ValueProviderSupport {

    private final static String[] VALUES = new String[]{
            "message1", "message2", "message3"
    };

    @Override
    public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext completionContext,
                                             String[] hints) {
        return Arrays.stream(VALUES).map(CompletionProposal::new).collect(Collectors.toList());
    }
}