/**
 * Copyright (c) 2010-2012 EBM WebSourcing, 2012-2013 Linagora
 * 
 * This program/library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This program/library 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 Lesser General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see <http://www.gnu.org/licenses/>
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.cli.shell;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jline.console.completer.Completer;
import jline.console.completer.StringsCompleter;

import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.ow2.petals.cli.api.command.Command;

/**
 * @author Nicolas Oddoux - EBM WebSourcing
 */
public class CommandCompleter implements Completer {

    private final Map<String, Command> commands;

    public CommandCompleter(Map<String, Command> commands) {
        this.commands = commands;
    }

    @Override
    public int complete(String buffer, int cursor, List<CharSequence> candidates) {
        //TODO handle quotes

        String lineBegin = buffer.substring(0, cursor);
        int lastSpaceIndex = lineBegin.lastIndexOf(" ");

        if(lastSpaceIndex == -1) {
            // first word of the line => completion for the command
            String cmdName = lineBegin.substring(0, cursor);
            Completer cmdCompleter = new StringsCompleter(this.commands.keySet());
            int completionIndex = cmdCompleter.complete(cmdName, cursor, candidates);
            removeUselessSpace(buffer, cursor, candidates);
            return completionIndex;
        } else if(lastSpaceIndex == cursor - 1) {
            // the previous character is a space
            return completeOptionOrArgument(buffer, cursor, candidates, lineBegin, lastSpaceIndex, "");
        } else {
            // completion for the argument and the options
            String lastWordToComplete = lineBegin.substring(lastSpaceIndex + 1, cursor);
            return completeOptionOrArgument(buffer, cursor, candidates, lineBegin, lastSpaceIndex, lastWordToComplete);
        }
    }

    protected int completeOptionOrArgument(String buffer, int cursor,
            List<CharSequence> candidates, String lineBegin, int lastSpaceIndex,
            String lastWordToComplete) {
        int firstSpaceIndex = buffer.indexOf(" ");
        String cmdName = buffer.substring(0, firstSpaceIndex);

        if (this.commands.containsKey(cmdName)) {
            final Command command = this.commands.get(cmdName);

            // looking for the previous option
            int optionArgNb = 0;
            int previousSpaceIndex = lastSpaceIndex;
            String previousWord = null;
            do {
                lineBegin = lineBegin.substring(0, previousSpaceIndex);
                optionArgNb++;
                lastSpaceIndex = previousSpaceIndex;
                previousSpaceIndex = lineBegin.lastIndexOf(" ");

                if (previousSpaceIndex == -1) {
                    // there is no option in the previous words
                    return completeOptionPossibilityOrAgument(buffer, cursor, candidates, command,
                            lastWordToComplete);
                }

                previousWord = lineBegin.substring(previousSpaceIndex + 1, lastSpaceIndex);
            } while (!previousWord.startsWith("-"));

            Options options = command.getOptions();
            if (options.hasOption(previousWord)) {
                // use short option to get the completer in the map
                Option option = options.getOption(previousWord);
                if (option.getArgs() >= optionArgNb) {
                    return completeOptionValue(buffer, cursor, candidates, command,
                            option, lastSpaceIndex);
                } else {
                    return completeOptionPossibilityOrAgument(buffer, cursor, candidates, command,
                            lastWordToComplete);
                }
            }
        }

        return -1;
    }

    protected static final int completeOptionValue(String buffer, int cursor,
            List<CharSequence> candidates, final Command command, Option option, int lastSpaceIndex) {

        final Map<String, Completer> optionCompleters = command.getOptionCompleters();
        if (optionCompleters != null) {
            Completer completer = optionCompleters.get(option.getOpt());

            if (completer != null) {
                // use the option completer
                String expressionToComplete = buffer.substring(lastSpaceIndex + 1, cursor);
                int completionIndex = completer.complete(expressionToComplete, cursor, candidates);
                removeUselessSpace(buffer, cursor, candidates);
                return cursor - expressionToComplete.length() + completionIndex;
            } else {
                return -1;
            }
        } else {
            return -1;
        }
    }

    private static final int completeOptionPossibilityOrAgument(String buffer, int cursor,
            List<CharSequence> candidates, final Command command, String lastWordToComplete) {
        if(lastWordToComplete.startsWith("-")) {
            // the last word is an option => use a string completer with all the possible options
            List<String> optionStrings = new ArrayList<String>();
            Options options = command.getOptions();
            for(Object optionObj : options.getOptions()) {
                Option option =(Option) optionObj;
                String shortOption = option.getOpt();
                if(shortOption != null) {
                    String opt = "-" + shortOption;
                    optionStrings.add(opt);
                }
                String longOpt = option.getLongOpt();
                if(longOpt != null) {
                    String opt = "--" + longOpt;
                    optionStrings.add(opt);
                }
            }
            Completer optionCompleter = new StringsCompleter(optionStrings);
            int completionIndex = optionCompleter.complete(lastWordToComplete, cursor, candidates);
            removeUselessSpace(buffer, cursor, candidates);
            return cursor - lastWordToComplete.length() + completionIndex;
        } else {
            // the last word is not an option => use the default completer
            Completer completer = command.getDefaultCompleter();

            if(completer != null) {
                int completionIndex = completer.complete(lastWordToComplete, cursor, candidates);
                removeUselessSpace(buffer, cursor, candidates);
                return cursor - lastWordToComplete.length() + completionIndex;
            }
        }

        return -1;
    }

    private static final void removeUselessSpace(String buffer, int cursor, List<CharSequence> candidates) {
        if(cursor < buffer.length() && candidates.size() == 1 && buffer.charAt(cursor) == ' ') {
            CharSequence candidate = candidates.get(0);
            candidate = candidate.subSequence(0, candidate.length() - 1);
            candidates.set(0, candidate);
        }
    }
}
