package fxlauncher;

import javafx.application.Application;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Implementation Application.Parameters that wraps the parameters given to the application
 * at startup, and adds any manifest configured parameters unless they were overriden
 * by the command line.
 */
public class LauncherParams extends Application.Parameters {
    private final List<String> rawArgs = new ArrayList<>();
    private final Map<String, String> namedParams = new HashMap<>();
    private final List<String> unnamedParams = new ArrayList<>();

    public LauncherParams(List<String> rawArgs) {
        this.rawArgs.addAll(rawArgs);
        computeParams();
    }

    public LauncherParams(Application.Parameters delegate, FXManifest manifest) {
        // Add all raw args from the parent application
        rawArgs.addAll(delegate.getRaw());

        // Add parameters from the manifest unless they were already specified on the command line
        if (manifest.parameters != null) {
            for (String arg : manifest.parameters.split("\\s")) {
                if (arg != null) {
                    if (rawArgs.contains(arg))
                        continue;

                    if (arg.startsWith("--") && arg.contains("=")) {
                        String argname = arg.substring(0, arg.indexOf("="));
                        if (rawArgs.stream().filter(a -> a.startsWith(argname)).findAny().isPresent())
                            continue;
                    }

                    rawArgs.add(arg);
                }
            }
        }

        computeParams();
    }

    private void computeParams() {
        // Compute named and unnamed parameters
        computeNamedParams();
        computeUnnamedParams();
    }

    public List<String> getRaw() {
        return rawArgs;
    }

    public List<String> getUnnamed() {
        return unnamedParams;
    }

    public Map<String, String> getNamed() {
        return namedParams;
    }

    /**
     * Returns true if the specified string is a named parameter of the
     * form: --name=value
     *
     * @param arg the string to check
     * @return true if the string matches the pattern for a named parameter.
     */
    private boolean isNamedParam(String arg) {
        return arg.startsWith("--") && (arg.indexOf('=') > 2 && validFirstChar(arg.charAt(2)));
    }

    /**
     * This method parses the current array of raw arguments looking for
     * name,value pairs. These name,value pairs are then added to the map
     * for this parameters object, and are of the form: --name=value.
     */
    private void computeNamedParams() {
        rawArgs.stream().filter(this::isNamedParam).forEach(arg -> {
            final int eqIdx = arg.indexOf('=');
            String key = arg.substring(2, eqIdx);
            String value = arg.substring(eqIdx + 1);
            namedParams.put(key, value);
        });
    }
    /**
     * This method computes the list of unnamed parameters, by filtering the
     * list of raw arguments, stripping out the named parameters.
     */
    private void computeUnnamedParams() {
        unnamedParams.addAll(rawArgs.stream().filter(arg -> !isNamedParam(arg)).collect(Collectors.toList()));
    }

    /**
     * Validate the first character of a key. It is valid if it is a letter or
     * an "_" character.
     *
     * @param c the first char of a key string
     * @return whether or not it is valid
     */
    private boolean validFirstChar(char c) {
        return Character.isLetter(c) || c == '_';
    }
}