/*
 * Copyright (c) 2002-2019, the original author(s).
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * https://opensource.org/licenses/BSD-3-Clause
 */
package org.jline.reader.impl.completer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import org.jline.reader.Candidate;
import org.jline.reader.Completer;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;

/**
 * A {@link Completer} implementation that invokes a child completer using the appropriate <i>separator</i> argument.
 * This can be used instead of the individual completers having to know about argument parsing semantics.
 *
 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
 * @since 2.3
 */
public class ArgumentCompleter implements Completer {
    private final List<Completer> completers = new ArrayList<>();

    private boolean strict = true;
    private boolean strictCommand = true;

    /**
     * Create a new completer.
     *
     * @param completers The embedded completers
     */
    public ArgumentCompleter(final Collection<Completer> completers) {
        Objects.requireNonNull(completers);
        this.completers.addAll(completers);
    }

    /**
     * Create a new completer.
     *
     * @param completers The embedded completers
     */
    public ArgumentCompleter(final Completer... completers) {
        this(Arrays.asList(completers));
    }

    /**
     * If true, a completion at argument index N will only succeed
     * if all the completions from 1-(N-1) also succeed.
     *
     * @param strictCommand the strictCommand flag
     */
    public void setStrictCommand(final boolean strictCommand) {
        this.strictCommand = strictCommand;
    }

    /**
     * Returns whether a completion at argument index N will success
     * if all the completions from arguments 0-(N-1) also succeed.
     *
     * @return True if strict.
     * @since 2.3
     */
    public boolean isStrict() {
        return this.strict;
    }

    /**
     * If true, a completion at argument index N will only succeed
     * if all the completions from 0-(N-1) also succeed.
     *
     * @param strict the strict flag
     */
    public void setStrict(final boolean strict) {
        this.strict = strict;
    }

    /**
     * Returns the list of completers used inside this <code>ArgumentCompleter</code>.
     *
     * @return The list of completers.
     * @since 2.3
     */
    public List<Completer> getCompleters() {
        return completers;
    }

    public void complete(LineReader reader, ParsedLine line, final List<Candidate> candidates) {
        Objects.requireNonNull(line);
        Objects.requireNonNull(candidates);

        if (line.wordIndex() < 0) {
            return;
        }

        List<Completer> completers = getCompleters();
        Completer completer;

        // if we are beyond the end of the completers, just use the last one
        if (line.wordIndex() >= completers.size()) {
            completer = completers.get(completers.size() - 1);
        } else {
            completer = completers.get(line.wordIndex());
        }

        // ensure that all the previous completers are successful before allowing this completer to pass (only if
        // strict).
        for (int i = strictCommand ? 0 : 1; isStrict() && (i < line.wordIndex()); i++) {
            int idx = i >= completers.size() ? (completers.size() - 1) : i;
            if (idx == 0 && !strictCommand) {
                continue;
            }
            Completer sub = completers.get(idx);
            List<? extends CharSequence> args = line.words();
            String arg = (args == null || i >= args.size()) ? "" : args.get(i).toString();

            List<Candidate> subCandidates = new LinkedList<>();
            sub.complete(reader, new ArgumentLine(arg, arg.length()), subCandidates);

            boolean found = false;
            for (Candidate cand : subCandidates) {
                if (cand.value().equals(arg)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                return;
            }
        }

        completer.complete(reader, line, candidates);
    }

    public static class ArgumentLine implements ParsedLine {
        private final String word;
        private final int cursor;

        public ArgumentLine(String word, int cursor) {
            this.word = word;
            this.cursor = cursor;
        }

        @Override
        public String word() {
            return word;
        }

        @Override
        public int wordCursor() {
            return cursor;
        }

        @Override
        public int wordIndex() {
            return 0;
        }

        @Override
        public List<String> words() {
            return Collections.singletonList(word);
        }

        @Override
        public String line() {
            return word;
        }

        @Override
        public int cursor() {
            return cursor;
        }
    }
}
