package com.jscape.util.cli.ext;

import java.util.Iterator;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * An arbitrary pair option. Can be used for parsing arbitrary command line argument pairs.
 * Must be specified after all concrete options in <code>CommandLineFactory.parse()</code> call.
 *
 * @author Alex Usun
 */
public class ArbitraryPairOption
        implements Option {

    private String namePrefix;
    private String name;
    private String value;

    public ArbitraryPairOption(String namePrefix, String name, String value) {
        assertNotNull(namePrefix);
        this.namePrefix = namePrefix;

        this.name = name;
        this.value = value;
    }

    /**
     * Creates a new option instance.
     *
     * @param namePrefix an option name prefix
     */
    public ArbitraryPairOption(String namePrefix) {
        assertNotNull(namePrefix);
        this.namePrefix = namePrefix;
    }

    public ArbitraryPairOption(ArbitraryPairOption that) {
        this(that.namePrefix, that.name, that.value);
    }

    /**
     * Returns the option name prefix.
     *
     * @return the option name prefix
     */
    public String namePrefix() {
        return this.namePrefix;
    }

    public boolean present() {
        return this.value != null;
    }

    public void reset() {
        this.name = null;
        this.value = null;
    }

    /**
     * Returns the current option name.
     *
     * @return the current option name
     */
    public String name() {
        return this.name;
    }

    /**
     * Returns the current option value.
     *
     * @return the current option value
     */
    public String value() {
        return this.value;
    }

    /**
     * Sets an option value.
     *
     * @param name  an option name
     * @param value an option value
     */
    public void init(String name, String value) {
        assertNotNull(name);
        this.name = name;

        assertNotNull(value);
        this.value = value;
    }

    public ArbitraryPairOption copy() {
        return new ArbitraryPairOption(this);
    }

    @Override
    public String toString() {
        return "ArbitraryPairOption {"
                + "prefix='" + this.namePrefix + '\''
                + ", name='" + this.name + '\''
                + ", value='" + this.value + '\''
                + '}';
    }

    /**
     * An arbitrary pair option parser.
     */
    public static class Format
            implements OptionFormat {

        public void parse(Option option, Arguments arguments)
                throws InvalidArgumentsException {
            ArbitraryPairOption targetOption = (ArbitraryPairOption) option;
            String optionNamePrefix = targetOption.namePrefix();

            for (Iterator<Argument> i = arguments.iterator(); i.hasNext();) {
                Argument argument = i.next();

                if (!argument.parsed() && argument.value().startsWith(optionNamePrefix) && i.hasNext()) {
                    Argument valueArgument = i.next();
                    assertNotParsed(targetOption, valueArgument);
                    initOption(targetOption, argument, valueArgument);
                    argument.setParsed();
                    valueArgument.setParsed();

                    return;
                }
            }
        }

        private void assertNotParsed(ArbitraryPairOption option, Argument argument)
                throws InvalidArgumentsException {
            if (argument.parsed()) {
                throw new InvalidArgumentsException(String.format("Option [%s] does not have required argument.", option.name()));
            }
        }

        private void initOption(ArbitraryPairOption option, Argument nameArgument, Argument valueArgument)
                throws InvalidArgumentsException {
            String name = nameArgument.value().substring(option.namePrefix().length());
            assertNameValid(name, nameArgument.value());
            option.init(name, valueArgument.value());
        }

        private void assertNameValid(String name, String fullName)
                throws InvalidArgumentsException {
            if (name.length() == 0) {
                throw new InvalidArgumentsException("Invalid arbitrary option name: " + fullName);
            }

        }

    }
}
