// Copyright 2007 Hitachi Data Systems
// All Rights Reserved.
//
// 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.archivas.clienttools.arcmover.cli;

import com.archivas.clienttools.arcutils.api.ArcMoverFactory;
import com.archivas.clienttools.arcutils.config.HCPMoverProperties;
import com.archivas.clienttools.arcutils.profile.AbstractProfileBase;
import com.archivas.clienttools.arcutils.profile.FileSystemProfile;
import com.archivas.clienttools.arcutils.profile.ProfileManager;
import com.archivas.clienttools.arcutils.utils.net.SSLCertChain;
import com.archivas.clienttools.arcutils.utils.net.SSLCertificateCallback;
import org.apache.commons.cli.*;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class AbstractArcCli {
    public static final String PACKAGE_NAME = AbstractArcCli.class.getPackage().getName();
    public static final String CLASS_FULL_NAME = AbstractArcCli.class.getName();
    public static final String CLASS_NAME = CLASS_FULL_NAME.substring(PACKAGE_NAME.length() + 1);
    public static Logger LOG = Logger.getLogger(CLASS_FULL_NAME);

    public static String NEWLINE = System.getProperty("line.separator");
    public static String LFS = "LFS"; // Alternation name for the local file system

    protected static String INSECURE_OPTION = "insecure";

    public static final int EXIT_CODE_OPTION_PARSE_ERROR = 1;
    // no longer used
    // public static final int EXIT_CODE_SSL_CERT_NOT_TRUSTED = 2;
    // public static final int EXIT_CODE_UNKNOWN_HOST = 3;
    public static final int EXIT_CODE_DM_ERROR = 4;
    public static final int EXIT_CODE_DM_FAILED_FILES = 5;

    public static String commandName = "hcpdm";

    private String args[];
    private int exitCode = 0;

    protected boolean allowInsecureSSL = false;

    protected AbstractArcCli(String args[]) {
        // Make sure the URL protocols get initialized.
        ArcMoverFactory.getInstance();

        this.args = args;

        // If we are on a non-windows system the command name is hcpdm.sh
        if (!System.getProperty("os.name").contains("Windows")) {
            commandName = "hcpdm.sh";
        }
    }

    class ArcCliSSLCertificateCallback implements SSLCertificateCallback {

        private boolean allowInsecureSSL = false;

        ArcCliSSLCertificateCallback(boolean flag) {
            this.allowInsecureSSL = flag;
        }

        public void validCertCallback(AbstractProfileBase profile, SSLCertChain certChain) {
            LOG.log(Level.FINE, "validCertCallback for profile " + profile + ": " + certChain);
            profile.setSSLCertChain(certChain);
        }

        public AllowSSLCert exceptionCallback(AbstractProfileBase profile, SSLCertChain certChain,
                                              String warningString) {
            return allowInsecureSSL ? AllowSSLCert.THIS_SESSION_ONLY : AllowSSLCert.NO;
        }
    };

    public void initializeProfiles(final boolean allowInsecureSSL) {
        ProfileManager.initialize(new ArcCliSSLCertificateCallback(allowInsecureSSL));
        this.allowInsecureSSL = allowInsecureSSL;
    }

    public int getExitCode() {
        return exitCode;
    }

    public void setExitCode(final int exitCode) {
        this.exitCode = exitCode;
    }

    public void exit() {
        System.exit(getExitCode());
    }

    public abstract Options getOptions();

    public abstract String getHelpFooter();

    public abstract String getHelpHeader();

    public abstract String getHelpUsageLine();

    protected static Map<String, Integer> cliOrder = new HashMap<String, Integer>();

    public class OptionComparator implements Comparator<Option> {
        public int compare(Option one, Option two) {
            if (one == two || one.equals(two)) {
                return 0;
            }

            Integer rankOne = cliOrder.get(one.getLongOpt());
            Integer rankTwo = cliOrder.get(two.getLongOpt());
            if (rankOne == null || rankTwo == null) {
                LOG.severe("New options need to be added to the cliOrder list in your cli class.");
                return 0;
            }

            int result;
            if (rankOne < rankTwo) {
                result = -1;
            } else {
                result = 1;
            }

            return result;
        }

    }

    public String helpScreen() {
        StringBuffer usage = new StringBuffer();
        HelpFormatter formatter = new HelpFormatter();

        if (cliOrder != null && cliOrder.size() > 0) {
            formatter.setOptionComparator(new OptionComparator());
        }

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        formatter.printHelp(pw,
                            HCPMoverProperties.CLI_WIDTH.getAsInt(),
                            getHelpUsageLine(),
                            null /* header */,
                            getOptions(),
                            HelpFormatter.DEFAULT_LEFT_PAD /* leftPad */,
                            HelpFormatter.DEFAULT_DESC_PAD /* descPad */,
                            null /* footer */,
                            false /* autoUsage */
        );

        usage.append(getHelpHeader());
        usage.append(sw.toString());
        usage.append(getHelpFooter());
        return usage.toString();
    }

    protected abstract void parseArgs() throws ParseException;

    public abstract void execute(PrintWriter out, PrintWriter err) throws Exception;

    public String[] getArgs() {
        return args;
    }

    public static String getProfileNameFromCmdLineAndValidateExistance(CommandLine cmdLine,
                                                                       String option)
            throws ParseException {
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;
        if (value != null) {
            retval = validateProfileNameExists(value);
        }
        if (retval == null) {
            throw new ParseException("Invalid " + option + " : " + value);
        }
        return retval;
    }

    public static String validateProfileNameExists(String value) throws ParseException {
        // Fix up local file system here, we also take LFS
        if (value.equalsIgnoreCase(LFS)) {
            value = FileSystemProfile.DEFAULT_FILESYSTEM_PROFILE_NAME;
        }

        AbstractProfileBase profile = ProfileManager.getProfileByName(value);
        if (profile == null) {
            throw new ParseException("Profile does not exist with name:  " + value);
        }

        return value;
    }

    public static String getProfileNameAndValidateItDoesNotExist(CommandLine cmdLine, String option)
            throws ParseException {
        if (!cmdLine.hasOption(option)) {
            throw new ParseException("Missing Option: " + option);
        }

        String value = cmdLine.getOptionValue(option);
        String retval = null;

        if (value != null) {
            AbstractProfileBase profile = ProfileManager.getProfileByName(value);
            if (profile == null) {
                retval = value;
            } else {
                throw new ParseException("Profile already exists with name:  " + value);
            }

        }
        if (retval == null) {
            throw new ParseException("Invalid " + option + " : " + value);
        }
        return retval;
    }

    public static Option getInsecureSSLOption() {
        return new Option(null, INSECURE_OPTION, false,
                "Tells HCP-DM to accept self-signed and mismatched SSL server certificates.");
    }

}