package uk.me.nxg.unity;

/**
 * Describes a ‘known’' function.  Functions take a single unit as argument.
 * <p>There are no syntax variations, beyond the set of functions which are recommended.
 */
public class FunctionDefinition
        implements Comparable<FunctionDefinition> {
    private final String description; // a description, such as 'base-10 logarithm'
    private final String latexForm;   // special LaTeX form, if different from 'name'
    private final String name;

    FunctionDefinition(String description, String latexForm, String name) {
        if (description == null || latexForm == null || name == null) {
            throw new IllegalArgumentException("FunctionDefinition must have non-null initialisers: "
                                               + description + "/"
                                               + latexForm + "/"
                                               + name);
        }
        this.description = description;
        this.latexForm = latexForm;
        this.name = name;

        assert description != null && latexForm != null;
    }

    /**
     * A human-readable description of the function.
     * @return a string description
     */
    public String description() {
        return description;
    }

    /**
     * A LaTeX form of the function name.
     * @return a LaTeX representation of the function
     */
    public String latexForm() {
        return latexForm;
    }

    @Override public int hashCode() {
        return description.hashCode();
    }

    /**
     * Produces a representation of this function as a string.  This
     * should not generally be used for formatting expressions: for that, use
     * {@link UnitExpr#toString}.
     * @return a string representation of the function
     */
    @Override public String toString() {
        return '<' + description + '>';
    }

    /**
     * The name of this function, or null.  The response will be null
     * if the function definition doesn't have enough syntax
     * information to know what the function is called in a given
     * syntax.  The syntax in question is that associated with the
     * function when it was parsed, which may not, of course, be the
     * syntax associated with it when it is written.
     * @return a null string
     */
    public String name() {
        return null;
    }

    /**
     * A fallback name for the function.  <p>In fact, this is (a) only
     * necessary for debugging purposes, because usually the
     * overridden name() function will work, and (b) it is always (?)
     * the same as the string eventually returned from name(), because
     * (at present) all of the functions have the same short names in
     * those syntaxes which know about them.  This may be unnecessary
     * generality (YAGNI, and all that), but it's essentially here for
     * symmetry with the UnitDefinition lookup process, here and in
     * ParseFunctions.java
     * @return a purely text representation of the function
     */
    public String fallbackName() {
        return name;
    }

    @Override public boolean equals(Object o) {
        if (o instanceof FunctionDefinition) {
            return compareTo((FunctionDefinition)o) == 0;
        } else {
            return false;
        }
    }

    // I think this is necessary if we want to have FunctionDefinition objects as hash keys
    /**
     * Provides a sorting order for definitions.
     * The order is well-defined but is not specified here.
     */
    @Override public int compareTo(FunctionDefinition o) {
        assert description != null;
        assert o.description != null;
        return description.compareTo(o.description);
    }

    /**
     * A factory for FunctionDefinition instances.  This is provided so that the
     * parser can construct these instances without having to be
     * given, or care about, the resolver object and its associated syntax.
     */
    static class Maker {
        final FunctionDefinitionMap.Resolver functionResolver;

        Maker(FunctionDefinitionMap.Resolver functionResolver) {
            this.functionResolver = functionResolver; // it's OK for this to be null
        }
        /**
         * Construct a FunctionDefinition from a string function name.
         * @param fn the string name of the function
         * @return a FunctionDefinition corresponding to the string name,
         * or null if no such function exists in this syntax.
         */
        public FunctionDefinition make(String fn) {
            FunctionDefinition d = functionResolver.getFunctionDefinition(fn);
            if (d == null) {
                return null;
            } else {
                String fnname = functionResolver.getFunctionName(d);
                if (fnname == null) {
                    return d;
                } else {
                    return new FunctionDefinitionInSyntax(d.description(),
                                                          d.latexForm(),
                                                          fnname);
                }
            }
        }
    }
}
