package picocli.codegen.annotation.processing.internal;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.IDefaultValueProvider;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;
import picocli.codegen.annotation.processing.ITypeMetaData;
import picocli.codegen.util.Assert;

import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeMirror;

/**
 * Implementation of the {@link IDefaultValueProvider} interface that provides metadata on the
 * {@code @Command(defaultValueProvider = xxx.class)} annotation.
 *
 * @since 4.0
 */
public class DefaultValueProviderMetaData implements IDefaultValueProvider, ITypeMetaData {

    private final TypeMirror typeMirror;

    public DefaultValueProviderMetaData() {
        this.typeMirror = null;
    }
    public DefaultValueProviderMetaData(TypeMirror typeMirror) {
        this.typeMirror = Assert.notNull(typeMirror, "typeMirror");
    }

    /**
     * Sets the specified {@code CommandSpec}'s
     * {@linkplain CommandSpec#defaultValueProvider(CommandLine.IDefaultValueProvider) default value provider}
     * to an {@code DefaultValueProviderMetaData} instance if the annotation attribute was present on the
     * specified {@code Command} annotation.
     *
     * @param result the command spec to initialize
     * @param cmd the {@code @Command} annotation to inspect
     */
    public static void initDefaultValueProvider(CommandSpec result, Command cmd) {
        try {
            // this is a hack to get access to the TypeMirror of the default provider class
            cmd.defaultValueProvider();
        } catch (MirroredTypeException ex) {
            DefaultValueProviderMetaData provider = new DefaultValueProviderMetaData(ex.getTypeMirror());
            if (!provider.isDefault()) {
                result.defaultValueProvider(provider);
            }
        }
    }

    /**
     * Returns {@code true} if the command did not have a {@code defaultValueProvider} annotation attribute.
     * @return {@code true} if the command did not have a {@code defaultValueProvider} annotation attribute.
     */
    public boolean isDefault() {
        return typeMirror == null || "picocli.CommandLine.NoDefaultProvider".equals(getTypeElement().getQualifiedName().toString());
    }

    /**
     * Returns the TypeMirror that this DefaultValueProviderMetaData was constructed with.
     * @return the TypeMirror of the {@code @Command(defaultValueProvider = xxx.class)} annotation.
     */
    public TypeMirror getTypeMirror() {
        return typeMirror;
    }

    public TypeElement getTypeElement() {
        return (TypeElement) ((DeclaredType) typeMirror).asElement();
    }

    /** Always returns {@code null}. */
    @Override
    public String defaultValue(ArgSpec argSpec) { return null; }

    /**
     * Returns a string representation of this object, for debugging purposes.
     * @return a string representation of this object
     */
    @Override
    public String toString() {
        return String.format("%s(%s)", getClass().getSimpleName(), isDefault() ? "default" : typeMirror);
    }
}
