package uk.me.nxg.unity;

/**
 * A factory which associates string syntaxes with actual parsers.
 */
class SyntaxFactory {

    // closures are bloody hard in Java
    private static final java.util.Map<Syntax,SyntaxMaker> parsers;
    static {
        java.util.HashMap<Syntax,SyntaxMaker> m = new java.util.HashMap<Syntax,SyntaxMaker>();

        // The following statements should add a map entry for every
        // element of the Syntax enumeration – it's a programming
        // error if any are missed, and this is checked by assertions below.
        //
        // The exception-throwing implementations should be redundant,
        // as the readable/writable status of the syntax should be
        // checked in the createParser and createWriter methods below.
        m.put(Syntax.VOUNITS, new SyntaxMaker() {
                public Parser makeParser() { return new Parser_vounits(); }
                public UnitWriter makeWriter(final UnitExpr e) { return new UnitWriterVOUnits(e); }
            });
        m.put(Syntax.FITS, new SyntaxMaker() {
                public Parser makeParser() { return new Parser_fits(); }
                public UnitWriter makeWriter(final UnitExpr e) { return new UnitWriterFits(e); }
            });
        m.put(Syntax.OGIP, new SyntaxMaker() {
                public Parser makeParser() { return new Parser_ogip(); }
                public UnitWriter makeWriter(final UnitExpr e) { return new UnitWriterOgip(e); }
            });
        m.put(Syntax.CDS, new SyntaxMaker() {
                public Parser makeParser() { return new Parser_cds(); }
                public UnitWriter makeWriter(final UnitExpr e) { return new UnitWriterCds(e); }
            });
        m.put(Syntax.LATEX, new SyntaxMaker() {
                public Parser makeParser()
                        throws UnitParserException {
                    throw new UnitParserException("Can't create a LaTeX parser");
                }
                public UnitWriter makeWriter(final UnitExpr e) { return new UnitWriterLaTeX(e); }
            });
        m.put(Syntax.DEBUG, new SyntaxMaker() {
                public Parser makeParser()
                        throws UnitParserException {
                    throw new UnitParserException("Can't create a debug parser");
                }
                public UnitWriter makeWriter(final UnitExpr e) { return new UnitWriterDebugging(e); }
            });
        m.put(Syntax.ALL, new SyntaxMaker() {
                public Parser makeParser()
                        throws UnitParserException {
                    throw new UnitParserException("Can't create a parser for syntax ALL: pick a syntax");
                }
                public UnitWriter makeWriter(final UnitExpr e)
                        throws UnwritableExpression {
                    throw new UnwritableExpression("Can't create a writer for syntax ALL: pick a syntax");
                }
            });

        parsers = java.util.Collections.unmodifiableMap(m);
    }

    private SyntaxFactory() {
        // does nothing -- prevent callers constructing instance
    }

    /**
     * Create a new parser, to parse a single input.
     *
     * @param syntax the type of parser required – one of the
     *     syntaxes of {@link Syntax}; must not be null
     * @param input the string to be parsed;
     *     can be null to simply create the parser
     * @return a Parser which can be called to parse the given input; never returns null
     * @throws UnitParserException if no parser is defined for this syntax
     */
    static Parser createParser(Syntax syntax, java.io.Reader input)
            throws UnitParserException {
        Parser parser;

        if (syntax == null) {
            throw new IllegalArgumentException("syntax must not be null");
        }
        if (! syntax.isReadable()) {
            throw new UnitParserException(String.format("Syntax '%s' is not readable", syntax));
        }

        SyntaxMaker sm = parsers.get(syntax);
        // We should have a SyntaxMaker for every syntax
        // -- that's a programming error if not
        assert sm != null;

        UnitDefinitionMap.Resolver unitResolver = UnitDefinitionMap.getInstance().getResolver(syntax);
        FunctionDefinitionMap.Resolver functionResolver = FunctionDefinitionMap.getInstance().getResolver(syntax);

        // the following will throw an exception if 'syntax'
        // indicates a write-only syntax (but this shouldn't happen,
        // since we've checked isReadable() above)
        parser = sm.makeParser();

        // if makeParser hasn't thrown an exception,
        // it should be impossible for resolver to be null
        assert unitResolver != null : String.format("parser for syntax %s failed to make a resolver",
                                                    syntax);

        // It's OK for functionResolver to be null,
        // since this merely indicates that there
        // is no set of functions defined for the syntax

        parser.setUnitMaker(new OneUnit.Maker(unitResolver));
        parser.setFunctionMaker(new FunctionDefinition.Maker(functionResolver));
        if (input != null) {
            parser.setReader(input, syntax);
        }

        return parser;
    }

    /**
     * Creates a writer for a single unit-expression
     * @param type the syntax of the string to be written
     * @param ue the single unit-expression which is to be written out
     * @return a writer
     * @throws UnwritableExpression if the syntax is not a writable one
     */
    static UnitWriter createWriter(Syntax type, UnitExpr ue)
            throws UnwritableExpression
    {
        if (type == null) {
            throw new IllegalArgumentException("syntax must not be null");
        }
        if (! type.isWritable()) {
            throw new UnwritableExpression(String.format("Syntax '%s' is not writable", type));
        }

        SyntaxMaker sm = parsers.get(type);

        // We should have a SyntaxMaker for every syntax
        // -- that's a programming error if not
        assert sm != null : type;

        return sm.makeWriter(ue);
    }
}
