package uk.me.nxg.unity;

import java.util.Map;

/**
 * Provides a mapping from unit abbreviations to unit definitions.
 * <p>This is a singleton class, so clients must first obtain the
 * instance of the class using {@link #getInstance}.
 */
public abstract class UnitDefinitionMap
{
    private static final long serialVersionUID =  44L;

    /**
     * The singleton instance of this class.
     */
    private static final UnitDefinitionMap singletonMap = new UnityUnitDefinitionMap().initialize();

    /**
     * Constructs a new unit-definition map.  This is protected on
     * purpose – instances should be retrieved using the
     * {@link #getInstance} method.
     */
    protected UnitDefinitionMap() {
        // nothing to do
    }

    /**
     * Obtain an instance of the unit-definition map.
     * @return the singleton UnitDefinitionMap
     */
    public static UnitDefinitionMap getInstance() {
        assert singletonMap != null;
        return singletonMap;
    }

    /**
     * Initialize an instance of the UnitDefinitionMap.  The class
     * guarantees that this method will be called before the
     * (singleton) instance is used.  Returns an instance of an
     * initialized UnitDefinitionMap, which becomes the singleton
     * instance, and which may or may not be the same as 'this' when
     * the initialiser is invoked.
     *
     * @return an instance of an initialized UnitDefinitionMap
     */
    abstract UnitDefinitionMap initialize();

    /**
     * Obtains a resolver for the given syntax.  This resolver will
     * look up symbol names in this syntax.
     * @param syntax the syntax to use
     * @return a Resolver, or null if the syntax is unrecognised
     */
    abstract Resolver getResolver(Syntax syntax);

    /**
     * Obtain unit-definition information for a symbol, as interpreted
     * in a particular syntax.  Return null if the syntax is
     * unrecognised, or if the symbol is not recognised within that syntax
     * @param symbol a symbol for a unit, without prefix (eg 'm' for
     * metre, not 'mm')
     *
     * If the syntax is passed as Syntax.ALL, then look up the
     * definition in all of the available readable syntaxes.
     * This may be of use when dealing with a ‘guessed’ unit.
     *
     * @param syntax one of the syntaxes of {@link Syntax}
     * @return a UnitDefinition instance, or null if this is not available
     */
    public UnitDefinition lookupUnitDefinition(Syntax syntax, String symbol) {
        UnitDefinition d = null;
        if (syntax == Syntax.ALL) {
            for (Syntax s : Syntax.values()) {
                if (s.isReadable()) {
                    d = lookupUnitDefinition(s, symbol);
                    if (d != null) break;
                }
            }
        } else {
            Resolver resolver = getResolver(syntax);
            if (resolver != null) {
                d = resolver.lookupSymbol(symbol);
            }
        }
        return d;
    }

    /**
     * Obtain unit-definition information for a symbol, as interpreted
     * in aany syntax.  Return null if the syntax is not recognised
     * within any syntax.
     *
     * <p>Typically, you will be more interested in
     * {@link #lookupUnitDefinition(Syntax,UnitDefinition)},
     *
     * @param symbol a symbol for a unit, without prefix (eg 'm' for metre, not 'mm')
     * @return a UnitDefinition instance, or null if this is not available
     */
    // public UnitDefinition lookupUnitDefinition(String symbol) {
    //     UnitDefinition d = null;
    //     for (Syntax s : Syntax.values()) {
    //         d = lookupUnitDefinition(s, symbol);
    //         if (d != null) break;
    //     }
    //     return d;
    // }

    /**
     * Obtain information about how a unit is represented in a particular syntax.
     * Return null if the syntax is unrecognised, or if the unit has
     * no representation within the given syntax.
     *
     * If the syntax is passed as Syntax.ALL, then look up the
     * definition in all of the available readable syntaxes.
     * This may be of use when dealing with a ‘guessed’ unit.
     *
     * @param syntax one of the known syntaxes
     * @param ud a UnitDefinition instance
     * @return representation information, or null if this is not available
     */
    public UnitRepresentation lookupUnitRepresentation(Syntax syntax, UnitDefinition ud) {
        UnitRepresentation r = null;
        if (syntax == Syntax.ALL) {
            for (Syntax s : Syntax.values()) {
                if (s.isReadable()) {
                    r = lookupUnitRepresentation(s, ud);
                    if (r != null) break;
                }
            }
        } else {
            Resolver resolver = getResolver(syntax);
            if (resolver != null) {
                r = resolver.lookupUnit(ud);
            }
        }
        return r;
    }

    /**
     * Obtain information about how a unit is represented, in any syntax.
     * Return null if the unit has no representation within the given syntax
     * (this should never happen).
     *
     * <p>Typically, you will be more interested in
     * {@link #lookupUnitRepresentation(Syntax,UnitDefinition)},
     * but this may be of use when dealing with a ‘guessed’ unit.
     *
     * @param ud a UnitDefinition instance
     * @return representation information, or null if this is not available
     */
    // public UnitRepresentation lookupUnitRepresentation(UnitDefinition ud) {
    //     UnitRepresentation r = null;
    //     for (Syntax s : Syntax.values()) {
    //         if (s.isReadable()) {
    //             r = lookupUnitRepresentation(s, ud);
    //             if (r != null) break;
    //         }
    //     }
    //     return r;
    // }

    /**
     * Obtain information about a unit, keyed by the unit's URI,
     * represented as a string.
     * @param uri the string representation of a unit' URI
     * @return a UnitDefinition instance, or null if this is not available
     */
    abstract public UnitDefinition lookupUnitDefinition(String uri);

    /**
     * A lookup object, which maps symbols to abstract units, in a
     * particular syntax.  This is partly a convenience class, but it
     * also acts as a closure, closing over the syntax and its
     * associated syntax-to-lookup mapping
     */
    interface Resolver {
        /**
         * Report the definition of the unit which the given symbol
         * maps to, in the syntax associated with this resolver.
         * @param symbol the symbol to be examined
         * @return a definition, or null if the symbol is unrecognised
         */
        public UnitDefinition lookupSymbol(String symbol);
        /**
         * Report representation information about the unit, in the
         * syntax associated with this resolver.
         * @param unit the unit to be examined
         * @return representation information, or null if this unit is
         * not recognised by this syntax
         */
        public UnitRepresentation lookupUnit(UnitDefinition unit);
        /**
         * Returns the syntax this resolver implements.
         * @return a syntax, which will not be null
         */
        public Syntax getSyntax();
    }

    /* The following two methods are associated with an earlier
       implementation of this class, which relied on the ../grammar
       build creating a serialized version of this map.  That's
       probably a useful idea, so keep these about for the moment. */
       /*
        * Persist a mapping of unit abbreviations to unit definitions.
        * The mapping is written to a Java serialization file, from which
        * it may subsequently be re-read by an instance of this class.
        *
        * <p>This method is public, but is not not expected to be used by
        * clients; it is used only when building this package.
        * FIXME: this is a poor design, and should be fixed when possible.
        */
    // public static void persist_unused(java.util.Map<String,UnitDefinition> m, java.io.File persistenceFile)
    //         throws java.io.IOException {
    //     // This is used by the program .../src/grammar/tools/ParseUnits.java.
    //     java.io.ObjectOutputStream oos
    //             = new java.io.ObjectOutputStream(new java.io.FileOutputStream(persistenceFile));
    //     UnitDefinitionMap newMap = new UnitDefinitionMap();
    //     newMap.unitMap.putAll(m);
    //     oos.writeObject(newMap);
    //     oos.close();
    //     // System.err.println("defs written to " + persistenceFile);
    //     // for (String uname : new java.util.TreeSet<String>(m.keySet())) {
    //     //     UnitDefinition u = m.get(uname);
    //     //     System.out.print(u.name() + ": ");
    //     //     String[] stxNames = { UnitParser.FITS, UnitParser.OGIP, UnitParser.CDS };
    //     //     for (String stxName : stxNames) {
    //     //         UnitRepresentation stx = u.getSyntax(stxName);
    //     //         if (stx != null) {
    //     //             System.out.print(stx + ";  ");
    //     //         }
    //     //     }
    //     //     System.out.println();
    //     // }

    //     return;
    // }

    // private UnitDefinitionMap initializeFromSerialization_unused() {

    //     String resourceName = "uk/me/nxg/unity/known-units-serialized";
    //     try {
    //         java.io.InputStream is = ClassLoader.getSystemResourceAsStream(resourceName);
    //         assert is != null; // this should be guaranteed by the build process

    //         java.io.ObjectInputStream ois = new java.io.ObjectInputStream(is);
    //         @SuppressWarnings("unchecked")
    //                 UnitDefinitionMap m = (UnitDefinitionMap)ois.readObject();
    //         ois.close();
    //         return m;
    //         // System.out.println("defs read from " + resourceName);
    //         // for (String name : new java.util.TreeSet<String>(singletonMap.unitMap.keySet())) {
    //         //     UnitDefinition u = singletonMap.unitMap.get(name);
    //         //     System.out.print(u.name() + ": ");
    //         //     String[] stxNames = { UnitParser.FITS, UnitParser.OGIP, UnitParser.CDS };
    //         //     for (String stxName : stxNames) {
    //         //         UnitRepresentation stx = u.getSyntax(stxName);
    //         //         if (stx != null) {
    //         //             System.out.print(stx + ";  ");
    //         //         }
    //         //     }
    //         //     System.out.println();
    //         // }
    //     } catch (java.io.IOException e) {
    //         System.err.println("Can't find resource " + resourceName + " (" + e + ")");
    //     } catch (java.lang.ClassNotFoundException e) {
    //         System.err.println("Can't deserialize unit information: " + e);
    //     }
    // }

}
