package uk.me.nxg.unity;

/**
 * Describes a unit.
 *
 * <p>A ‘unit’is a notion like ‘metre’, or ‘pixel’, and is not
 * dependent on a particular syntax.  The information here includes a
 * readable name for a quantity (such as ‘Metre’ or ‘Julian year’), a
 * URI uniquely naming it, and its dimensions.
 *
 * <p>The URI unique name is derived from the <a href='http://qudt.org/'>QUDT</a>
 * framework of quantities, units and dimensions, though it is not
 * restricted to the set of units and quantities defined there.
 *
 * <p>The syntax-specific aspects of describing units concern how the
 * unit is abbreviated, and indeed whether it is permitted or
 * recommended in a particular syntax, and this is described by the
 * class {@link UnitRepresentation}.
 */
public class UnitDefinition
        implements java.io.Serializable, Comparable<UnitDefinition> {
    private final String uri; // the URI name of the unit, such as http://qudt.org/vocab/unit#Meter
    private final String name; // the name of the unit, such as 'Metre' -- not the abbreviation 'm'
    private final String type; // type such as 'length'
    private final Dimensions dimensions;
    private final String description;   // for example 'Hz = s-1'
    private final String latexForm;   // special LaTeX form, if different from abbrev

    // The following is unlikely to change, but its presence keeps the compiler happy
    private static final long serialVersionUID = 42L;

    UnitDefinition(String uri,
                   String name,
                   String type,
                   String dimensionString,
                   String description,
                   String latexForm) {
        if (uri == null || name == null || type == null) {
            throw new IllegalArgumentException("UnitDefinition must have a non-null uri, name and type");
        }
        this.uri = uri;
        this.name = name;
        this.type = type;
        try {
            this.dimensions = Dimensions.parse(dimensionString);
        } catch (UnitParserException e) {
            throw new IllegalArgumentException("UnitDefinition given unparseable dimension string");
        }
        this.description = (description == null || description.length() == 0 ? null : description);
        this.latexForm = (latexForm == null || latexForm.length()==0) ? null : latexForm;

        // class invariant
        assert this.uri != null && this.name != null && this.type != null
                && (this.description == null || this.description.length() > 0)
                && (this.latexForm == null || this.latexForm.length() > 0);
    }

    // accessors
    /** The name of this unit, for example ‘metre’ */
    public String name()  {
        return name;
    }
    /** A description of the type of this unit, for example ‘length’ */
    public String type()  {
        return type;
    }
    /** Further remarks about this unit, or other comments */
    public String description() {
        return description;
    }
    /** A LaTeX version of the unit symbol, if there is one defined
     */
    public String latexForm() {
        return latexForm;
    }
    /** The dimensions of this unit */
    public Dimensions dimensions() {
        return dimensions;
    }
    /** 
     * The Kind of this unit, named by a URI
     * @return a string representing the unit's URI
     */
    public String getURI() {
        return uri;
    }
    
    /**
     * Return the syntax-specific information about this unit.
     * Returns null if the syntax is unknown for this unit, meaning
     * that the given syntax does not recognise this unit as a
     * recommended one.
     *
     * @param syntax a non-null string name for the syntax, which should be one
     * of the syntaxes of {@link Syntax}
     * @return the syntax details for this unit in this syntax, or null if the syntax is unknown
     */
    public UnitRepresentation getRepresentation(Syntax syntax) {
        if (syntax == null) {
            throw new IllegalArgumentException("Must specify a syntax for getRepresentation");
        }
        UnitDefinitionMap.Resolver r = UnitDefinitionMap.getInstance().getResolver(syntax);
        if (r == null) {
            return null;
        } else {
            return r.lookupUnit(this);
        }
    }

    /**
     * Return a representation of this unit, from any syntax that
     * knows of one.  Since every known unit appears in at least one
     * map, this will never return null.
     */
    public UnitRepresentation getRepresentation() {
        for (Syntax s : Syntax.values()) {
            UnitRepresentation r = getRepresentation(s);
            if (r != null) {
                return r;
            }
        }
        // ooops!
        assert false;
        return null;            // redundant
    }

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

    @Override public int compareTo(UnitDefinition o) {
        assert uri != null;
        assert o.uri != null;
        return uri.compareTo(o.uri);
    }

    /**
     * Produces a representation of this unit as a string.  This
     * should not generally be used for formatting expressions: for that, use
     * {@link UnitExpr#toString}.
     */
    public String toString() {
        return name + "(" + type + ")";
    }
}
