/*
 * Copyright (C) 2011 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package net.sourceforge.argparse4j.internal;

import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.argparse4j.helper.MessageLocalization;
import net.sourceforge.argparse4j.helper.TextHelper;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.FeatureControl;
import net.sourceforge.argparse4j.inf.Subparsers;

/**
 * <strong>The application code must not use this class directly.</strong>
 */
public final class SubparsersImpl implements Subparsers {

    private final ArgumentParserImpl mainParser_;
    /**
     * The key is subparser command or alias name and value is subparser object.
     * The real command and alias names share the same subparser object. To
     * identify the aliases, check key equals to
     * {@link SubparserImpl#getCommand()}. If they are equal, it is not alias.
     */
    private final Map<String, SubparserImpl> parsers_ = new LinkedHashMap<>();
    private String help_ = "";
    private String title_ = "";
    private String description_ = "";
    private String dest_ = "";
    private String metavar_ = "";

    SubparsersImpl(ArgumentParserImpl mainParser) {
        mainParser_ = mainParser;
    }

    @Override
    public SubparserImpl addParser(String command) {
        return addParser(command, true, mainParser_.getPrefixChars());
    }

    @Override
    public SubparserImpl addParser(String command, boolean addHelp) {
        return addParser(command, addHelp, mainParser_.getPrefixChars());
    }

    @Override
    public SubparserImpl addParser(String command, boolean addHelp,
            String prefixChars) {
        if (command == null || command.isEmpty()) {
            throw new IllegalArgumentException(
                    "command cannot be null or empty");
        } else if (parsers_.containsKey(command)) {
            throw new IllegalArgumentException(String.format(
                    TextHelper.LOCALE_ROOT,
                    "command '%s' has been already used", command));
        }
        SubparserImpl parser = new SubparserImpl(
                mainParser_.getConfig().forSubparser(addHelp, prefixChars),
                command, mainParser_);
        parsers_.put(command, parser);
        return parser;
    }

    @Override
    public SubparsersImpl dest(String dest) {
        dest_ = TextHelper.nonNull(dest);
        return this;
    }

    @Override
    public SubparsersImpl help(String help) {
        help_ = TextHelper.nonNull(help);
        return this;
    }

    @Override
    public SubparsersImpl title(String title) {
        title_ = TextHelper.nonNull(title);
        return this;
    }

    public String getTitle() {
        return title_;
    }

    @Override
    public SubparsersImpl description(String description) {
        description_ = TextHelper.nonNull(description);
        return this;
    }

    public String getDescription() {
        return description_;
    }

    @Override
    public SubparsersImpl metavar(String metavar) {
        metavar_ = TextHelper.nonNull(metavar);
        return this;
    }

    boolean hasSubCommand() {
        return !parsers_.isEmpty();
    }

    /**
     * Return true if SubparserImpl has at least one sub-command whose help
     * output is not suppressed.
     * 
     * @return true if SubparserImpl has at least one sub-command whose help
     *         output is not suppressed.
     */
    boolean hasNotSuppressedSubCommand() {
        for (Map.Entry<String, SubparserImpl> entry : parsers_.entrySet()) {
            if (entry.getValue().getHelpControl() != FeatureControl.SUPPRESS) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get next SubparserImpl from given command and state. This function
     * resolves abbreviated command input as well. If the given command is
     * ambiguous, {@link ArgumentParserException} will be thrown. If no matching
     * SubparserImpl is found, this function returns null.
     * 
     * @return next SubparserImpl or null
     */
    private SubparserImpl resolveNextSubparser(String command)
            throws ArgumentParserException {
        if (command.isEmpty()) {
            return null;
        }
        SubparserImpl ap = parsers_.get(command);
        if (ap == null) {
            List<String> cand = TextHelper.findPrefix(parsers_.keySet(),
                    command);
            int size = cand.size();
            if (size == 1) {
                ap = parsers_.get(cand.get(0));
            } else if (size > 1) {
                // Sort it to make unit test easier
                Collections.sort(cand);
                throw new ArgumentParserException(String.format(
                        TextHelper.LOCALE_ROOT,
                        localize("ambiguousCommandError"), command,
                        TextHelper.concat(cand, 0, ", ")), mainParser_);
            }
        }
        return ap;
    }

    void parseArg(ParseState state, Map<String, Object> opts)
            throws ArgumentParserException {
        if (parsers_.isEmpty()) {
            throw new IllegalArgumentException("too many arguments");
        }
        SubparserImpl ap = resolveNextSubparser(state.getArg());
        if (ap == null) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, SubparserImpl> entry : parsers_.entrySet()) {
                sb.append("'").append(entry.getKey()).append("', ");
            }
            sb.delete(sb.length() - 2, sb.length());
            throw new UnrecognizedCommandException(String.format(
                    TextHelper.LOCALE_ROOT,
                    localize("invalidChoiceError"), state.getArg(),
                    sb.toString()), mainParser_, state.getArg());
        } else {
            ++state.index;
            ap.parseArgs(state, opts);
            // Call after parseArgs to overwrite dest_ attribute set by
            // sub-parsers.
            if (!dest_.isEmpty()) {
                opts.put(dest_, ap.getCommand());
            }
        }
    }

    String formatShortSyntax() {
        if (metavar_.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            sb.append("{");
            for (Map.Entry<String, SubparserImpl> entry : parsers_.entrySet()) {
                if (entry.getValue().getHelpControl() != FeatureControl.SUPPRESS) {
                    sb.append(entry.getKey()).append(",");
                }
            }
            if (sb.length() > 1) {
                sb.delete(sb.length() - 1, sb.length());
            }
            sb.append("}");
            return sb.toString();
        } else {
            return metavar_;
        }
    }

    /**
     * Writes the help message for this and descendants.
     * 
     * @param writer
     *            The writer to output
     * @param format_width
     *            column width
     */
    void printSubparserHelp(PrintWriter writer, int format_width) {
        TextHelper.printHelp(writer, formatShortSyntax(), help_,
                mainParser_.getTextWidthCounter(), format_width);
        for (Map.Entry<String, SubparserImpl> entry : parsers_.entrySet()) {
            // Don't generate help for aliases.
            if (entry.getValue().getHelpControl() != FeatureControl.SUPPRESS &&
                    entry.getKey().equals(entry.getValue().getCommand())) {
                entry.getValue().printSubparserHelp(writer, format_width);
            }
        }
    }

    /**
     * Returns collection of the sub-command name under this object.
     * 
     * @return collection of the sub-command name
     */
    Collection<String> getCommands() {
        return parsers_.keySet();
    }

    /**
     * Adds Subparser alias names for given Subparser. For each SubparsersImpl
     * instance, alias names and commands must be unique. If duplication is
     * found, {@link IllegalArgumentException} is thrown.
     * 
     * @param subparser
     *            Subparser to add alias names
     * @param alias
     *            alias name
     */
    void addAlias(SubparserImpl subparser, String... alias) {
        for (String command : alias) {
            if (parsers_.containsKey(command)) {
                throw new IllegalArgumentException(String.format(
                        TextHelper.LOCALE_ROOT,
                        "command '%s' has been already used", command));
            } else {
                parsers_.put(command, subparser);
            }
        }
    }

    private String localize(String messageKey) {
        return MessageLocalization.localize(
                mainParser_.getConfig().getResourceBundle(), messageKey);
    }
}
