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.
     */
    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.
     */
    abstract UnitDefinitionMap initialize();

    /**
     * Obtains a resolver for the given syntax.  This resolver will
     * look up symbol names in this syntax.
     * @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')
     * @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) {
        Resolver r = getResolver(syntax);
        if (r == null) {
            return null;
        } else {
            return r.lookupSymbol(symbol);
        }
    }

    /**
     * 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.
         * @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.
         * @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.
         */
        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);
    //     }
    // }

}
