/*
 * Supports various other tests not covered by the tests in
 * ../../grammar/testcases.csv.  These are generally tests of the
 * java-specific parts of the interface, rather than tests of what
 * does and doesn't parse.
 */

import uk.me.nxg.unity.*;

import org.junit.Test;
import static org.junit.Assert.*;

public class TestMisc {

    @Test public void testSimpleFitsParse() throws Exception {
        UnitParser p = new UnitParser(Syntax.FITS, "cm/s");
        UnitExpr expr = p.getParsed();

        //System.out.println("cm/s -> " + expr.toDebugString());
        assertEquals("cm s-1", expr.toString());

        // FIXME: We could probably benefit from some more thorough
        // testing of the Dimensions arithmetic.
        Dimensions d = expr.getDimensions();
        int correctD[] = { 0,1,0,-1,0,0,0,0 };
        float[] actualFloatD = d.exponents();
        int[] actualD = new int[8];
        for (int i=0; i<8; i++) {
            actualD[i] = (int)actualFloatD[i];
        }
        assertArrayEquals(correctD, actualD);
        assertEquals("L T^-1", d.toString());
    }

    @Test public void testFitsParse() throws Exception {
        UnitParser p = new UnitParser(Syntax.FITS, "mm^3/(gnu s)");
        UnitExpr expr = p.getParsed();

        // the leading factor is 1.0; log(1.0)=0.0
        assertEquals("logFactor", 0.0, expr.getLogFactor(), 0);

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition metres = udm.lookupUnitDefinition(Syntax.FITS, "m");
        UnitDefinition seconds = udm.lookupUnitDefinition(Syntax.FITS, "s");
        assertNull(udm.lookupUnitDefinition(Syntax.FITS, "gnu"));

        // There are no Candelas in this unit
        UnitDefinition candelasCorrect = udm.lookupUnitDefinition(Syntax.FITS, "cd");
        UnitDefinition candelasWrong = udm.lookupUnitDefinition(Syntax.FITS, "Cd");
        assertNotNull(candelasCorrect);
        assertNull(candelasWrong);
        OneUnit u = expr.getUnit(candelasCorrect);
        assertNull(u);

        // while we're at it, check that getUnit objects to bad calls
        try {
            String test = null;
            OneUnit badU = expr.getUnit(test);
            fail("getUnit(null string) should fail");
        } catch (IllegalArgumentException e) {
            // that's OK
        } catch (Exception e) {
            fail("getUnit(null string) produced the wrong exception: " + e);
        }
        try {
            UnitDefinition test = null;
            OneUnit badU = expr.getUnit(test);
            fail("getUnit(null unit definition) should fail");
        } catch (IllegalArgumentException e) {
            // that's OK
        } catch (Exception e) {
            fail("getUnit(null unit definition) produced the wrong exception: " + e);
        }

        u = expr.getUnit("m");
        assertEquals(u, expr.getUnit(metres));
        assertTrue(u instanceof SimpleUnit); // XXX Is there a better junit test?
        SimpleUnit su = (SimpleUnit)u;

        assertEquals("Metre", u.getBaseUnitName());
        assertEquals("Metre", u.getBaseUnitDefinition().name());
        assertEquals("http://qudt.org/vocab/unit#Meter", su.getBaseUnitDefinition().getURI());
        assertEquals(metres, su.getBaseUnitDefinition());
        assertEquals("m-prefix", -3, su.getPrefix());
        assertEquals("m-power", 3.0, su.getExponent(), 0);

        u = expr.getUnit("s");
        assertEquals(u, expr.getUnit(seconds));

        assertTrue(u instanceof SimpleUnit); // XXX Is there a better junit test?
        su = (SimpleUnit)u;

        assertEquals("Second", su.getBaseUnitName());
        assertEquals("s-prefix", 0, su.getPrefix());
        assertEquals("s-power", -1.0, su.getExponent(), 0);

        assertEquals("mm3 gnu-1 s-1", expr.toString()); // FITS, but the docs don't guarantee this
        assertEquals("mm3 s-1 gnu-1", expr.canonicalize().toString());
        assertEquals("mm3/gnu/s", expr.toString(Syntax.CDS));
    }

    @Test public void testErgsFits() throws Exception {
        // ergs are
        //   permitted but deprecated in fits
        //   permitted and fine in ogip, and 
        //   unrecognised in cds
        UnitExpr expr = new UnitParser(Syntax.FITS, "erg").getParsed();
        assertNotNull(expr);
        assertTrue(expr.allUnitsRecognised(Syntax.FITS));
        assertFalse(expr.allUnitsRecommended(Syntax.FITS));
        assertTrue(expr.allUnitsRecognised(Syntax.OGIP));
        assertTrue(expr.allUnitsRecommended(Syntax.OGIP));
        assertFalse(expr.allUnitsRecognised(Syntax.CDS));
        assertFalse(expr.allUnitsRecommended(Syntax.CDS));

        OneUnit erg = expr.getUnit("erg");
        assertTrue(erg.isRecognisedUnit(Syntax.FITS));
        assertFalse(erg.isRecommendedUnit(Syntax.FITS));

        assertTrue(erg.isRecognisedUnit(Syntax.OGIP));
        assertTrue(erg.isRecommendedUnit(Syntax.OGIP));

        assertFalse(erg.isRecognisedUnit(Syntax.CDS));
        assertFalse(erg.isRecommendedUnit(Syntax.CDS));
        
        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition ergs = udm.lookupUnitDefinition(Syntax.FITS, "erg");

        assertEquals(erg, expr.getUnit(ergs));
        assertTrue(erg instanceof SimpleUnit);
        SimpleUnit serg = (SimpleUnit)erg;
        
        // FITS (which we parsed with) recognised "erg", and so
        // produces the abstract unit name here.
        assertEquals("Erg", serg.getBaseUnitName());
        assertEquals(ergs, serg.getBaseUnitDefinition());

        assertEquals("http://qudt.org/vocab/dimension#Dimension_SI_L2MT-2", expr.getDimensions().getURI());
    }

    @Test public void testErgsCds() throws Exception {
        // Re-do some of the tests of testErgsFits, with a different
        // parsing syntax

        UnitExpr expr = new UnitParser(Syntax.CDS, "erg").getParsed();
        assertNotNull(expr);
        // these are the same tests as in testErgsFits -- these
        // results should be independent of the parsing syntax (yes?)

        // ergs aren't a recognised unit in the CDS syntax
        assertNull(expr.getDimensions());

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition ergs = udm.lookupUnitDefinition(Syntax.CDS, "erg");
        assertNull(ergs);

        // we can look it up by string, even though this isn't a known unit
        OneUnit erg = expr.getUnit("erg");
        assertNotNull(erg);
        assertTrue(erg instanceof SimpleUnit);
        SimpleUnit serg = (SimpleUnit)erg;

        assertEquals("erg", serg.getBaseUnitName()); // ie (unknown) "erg" not "Erg"
        assertNull(serg.getBaseUnitDefinition()); // not a known unit
    }

    @Test public void preferredUnits() throws Exception {
        // Check recommended/deprecated status, and preferred output forms
        // FITS: accepts pixel and pix, prefers pixel
        // OGIP: accepts pixel
        // CDS:  accepts pix
        UnitExpr expr = new UnitParser(Syntax.FITS, "pix").getParsed();

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition pixels = udm.lookupUnitDefinition(Syntax.FITS, "pixel");

        OneUnit pix = expr.getUnit("pix");
        assertNotNull(pix);

        assertTrue(pix instanceof SimpleUnit);
        SimpleUnit spix = (SimpleUnit)pix;

        assertEquals(pixels, spix.getBaseUnitDefinition());
        assertEquals(pix, expr.getUnit(pixels));

        assertEquals("pixel", expr.toString(Syntax.FITS));
        assertEquals("pixel", expr.toString(Syntax.OGIP));
        assertEquals("pix",   expr.toString(Syntax.CDS));
    }

    @Test public void unpreferredUnits() throws Exception {
        // FITS recognises both "yr" and "a"
        UnitExpr exprFits = new UnitParser(Syntax.FITS, "Ga").getParsed();
        // OGIP recognises only "yr"
        UnitExpr exprOgipYr = new UnitParser(Syntax.OGIP, "Gyr").getParsed();
        // This is a billion "a"s -- nothing to do with years
        UnitExpr exprOgipA = new UnitParser(Syntax.OGIP, "Ga").getParsed();

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition years = udm.lookupUnitDefinition(Syntax.FITS, "yr");
        UnitDefinition annums = udm.lookupUnitDefinition(Syntax.FITS, "a");
        assertSame(years, annums);

        UnitDefinition years1 = udm.lookupUnitDefinition(Syntax.OGIP, "yr");
        UnitDefinition annums1 = udm.lookupUnitDefinition(Syntax.OGIP, "a");
        assertSame(years, years1);
        // OGIP doesn't recognise "a" as referring to years
        assertNull(annums1);

        // We can look up the FITS-parsed expression using units and
        // both of the string symbols which FITS permits for years
        OneUnit yearUnit = exprFits.getUnit(years);
        OneUnit yearString = exprFits.getUnit("yr");
        OneUnit annumUnit = exprFits.getUnit(annums);
        OneUnit annumString = exprFits.getUnit("a");
        assertEquals(yearUnit, yearString);
        assertEquals(yearUnit, annumUnit);
        assertEquals(yearUnit, annumString);

        OneUnit ogipYrYearUnit = exprOgipYr.getUnit(years);
        OneUnit ogipYrYearString = exprOgipYr.getUnit("yr");
        OneUnit ogipYrAnnumUnit = exprOgipYr.getUnit(annums);
        OneUnit ogipYrAnnumString = exprOgipYr.getUnit("a");
        assertEquals(yearUnit, ogipYrYearUnit);
        // these two are equal, but not the same object
        assertNotSame(yearUnit, ogipYrYearUnit);
        // but...
        assertSame(yearUnit.getBaseUnitDefinition(), ogipYrYearUnit.getBaseUnitDefinition());
        assertEquals(ogipYrYearUnit, ogipYrYearString);
        assertEquals(ogipYrYearUnit, ogipYrAnnumUnit); 
        // OGIP doesn't know about annums, so as far as it's
        // concerned, the "a" unit has nothing to do with "yr", and
        // didn't appear in the original string.
        assertNull(ogipYrAnnumString); 

        OneUnit ogipAYearUnit = exprOgipA.getUnit(years);
        OneUnit ogipAYearString = exprOgipA.getUnit("yr");
        OneUnit ogipAAnnumUnit = exprOgipA.getUnit(annums);
        OneUnit ogipAAnnumString = exprOgipA.getUnit("a");
        // OGIP doesn't recognise "a" as year, so none of the
        // following are recognised
        assertNull(ogipAYearUnit);
        assertNull(ogipAYearString);
        assertNull(ogipAAnnumUnit);
        // The ogipAAnnumString is a unit of "a"s, not a unit of years.
        assertNotNull(ogipAAnnumString);
        // but it's not a year, so...
        assertFalse(yearUnit.equals(ogipAAnnumString));

        assertEquals("Julian year", yearUnit.getBaseUnitDefinition().name());
        assertEquals("Julian year", yearUnit.getBaseUnitName());
        assertEquals("Julian year", ogipYrYearUnit.getBaseUnitName());
        assertEquals("a", ogipAAnnumString.getBaseUnitName()); // the mysterious "a" unit
    }
        

    @Test public void unusualUnits() throws Exception {
        // parse femto-furlong (just "furlong" gets parsed as femto-urlong)
        UnitExpr expr = new UnitParser(Syntax.FITS, "ffurlong").getParsed();

        OneUnit furlong = expr.getUnit("furlong");
        assertNotNull(furlong);

        assertNull(furlong.getBaseUnitDefinition()); // not a known unit

        assertEquals("ffurlong", expr.toString());
    }

    @Test(expected=UnitParserException.class) public void emptyUnits() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.FITS, "").getParsed();
    }

    // XXX I'm not at all sure what the following two cases should do.
    // Should "m/m" produce a dimensionless 'unit'?
    // Should "m*m^2" produce m^3?
    // Should "m * mm" produce 10^-3 m^2 ?
    /*
    @Test public void dubiousUnits1() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.FITS, "m*s*m^2").getParsed();

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition metres = udm.lookupUnitDefinition(Syntax.FITS, "m");
        UnitDefinition seconds = udm.lookupUnitDefinition(Syntax.FITS, "s");

        OneUnit u = expr.getUnit(metres);
        assertNotNull(u);
        assertEquals(u, expr.getUnit("m"));
        assertEquals(3.0, u.getExponent(), 0);

        u = expr.getUnit(seconds);
        assertNotNull(u);
        assertEquals(seconds, expr.getUnit("s"));
        assertEquals(1.0, u.getExponent(), 0);

        assertEquals("m^3/s", expr.toString());
    }

    @Test public void dubiousUnits2() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.FITS, "m/m").getParsed();

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition metres = udm.lookupUnitDefinition(Syntax.FITS, "m");

        OneUnit m = expr.getUnit(metres);
        assertNotNull(m);
        assertEquals(m, expr.getUnit("m"));

        assertEquals(0.0, m.getExponent(), 0);
        assertEquals("???", expr.toString());
    }
    */

    @Test public void unitOrdering() throws Exception {
        // These tests aren't an important part of the interface, and
        // the behaviour of the various compareTo methods isn't
        // documented, but they have an impact on, for example, the
        // way that unit expressions are serialised, so should remain
        // consistent.
        UnitExpr km = new UnitParser(Syntax.FITS, "km").getParsed();
        UnitExpr km__1 = new UnitParser(Syntax.FITS, "km-1").getParsed();
        UnitExpr km_2 = new UnitParser(Syntax.FITS, "km+2").getParsed();
        UnitExpr km__2 = new UnitParser(Syntax.FITS, "km-2").getParsed();
        UnitExpr s = new UnitParser(Syntax.FITS, "s").getParsed();
        UnitExpr foo = new UnitParser(Syntax.FITS, "foo").getParsed();
        UnitExpr wibble = new UnitParser(Syntax.FITS, "wibble").getParsed();
        UnitExpr log_km = new UnitParser(Syntax.FITS, "log(km)").getParsed();
        UnitExpr log_s = new UnitParser(Syntax.FITS, "log(s)").getParsed();
        UnitExpr log_foo = new UnitParser(Syntax.FITS, "log(foo)").getParsed();
        UnitExpr log_wibble = new UnitParser(Syntax.FITS, "log(wibble)").getParsed();
        UnitExpr xfn_km = new UnitParser(Syntax.FITS, "xfn(km)").getParsed();
        UnitExpr xfn_s = new UnitParser(Syntax.FITS, "xfn(s)").getParsed();
        UnitExpr xfn_foo = new UnitParser(Syntax.FITS, "xfn(foo)").getParsed();
        UnitExpr xfn_wibble = new UnitParser(Syntax.FITS, "xfn(wibble)").getParsed();

        UnitExpr cmps[] = {
            km, s,
            s, km__1,
            km, km_2,
            km, km__1,
            km__1, km__2,
            km, foo,
            foo, wibble,
            km, log_km,
            km, log_wibble,
            log_km, log_s,
            log_km, log_foo,
            log_foo, log_wibble,
            log_km, xfn_km,
            xfn_km, xfn_s,
            xfn_km, xfn_foo,
            xfn_foo, xfn_wibble,
        };
        for (int i=0; i<cmps.length; i+=2) {
            UnitExpr smaller = cmps[i];
            UnitExpr larger  = cmps[i+1];
            assertTrue(Integer.toString(i/2), smaller.compareTo(larger) < 0);
        }
    }
        
    @Test public void equalityTests() throws Exception {
        UnitExpr expr1 = new UnitParser(Syntax.VOUNITS, "km.s**-1").getParsed();
        UnitExpr expr2 = new UnitParser(Syntax.CDS, "km/s").getParsed();
        UnitExpr expr3 = new UnitParser(Syntax.OGIP, "s**(-1) km**1").getParsed();

        assertTrue(expr1.equals(expr2));
        assertTrue(expr2.equals(expr3));
        assertTrue(expr3.equals(expr1));

        UnitExpr expr2a = new UnitParser(Syntax.CDS, "km/s/Mpc").getParsed();
        assertFalse(expr2.equals(expr2a));
        assertFalse(expr2a.equals(expr2));

        UnitExpr expr4 = new UnitParser(Syntax.OGIP, "nm/m").getParsed();
        UnitExpr expr5 = new UnitParser(Syntax.FITS, "m-1*nm").getParsed();
        assertTrue(expr4.equals(expr5));
        assertTrue(expr5.equals(expr4));

        UnitExpr expr6 = new UnitParser(Syntax.FITS, "m*log(kg)").getParsed();
        UnitExpr expr7 = new UnitParser(Syntax.CDS, "[kg].m").getParsed();
        assertTrue(expr6.equals(expr7));
        assertTrue(expr7.equals(expr6));        
    }

    @Test public void outputInLocale() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.CDS, "0.1nm/s").getParsed();
        String cdsFormatDefault = expr.toString(Syntax.CDS);
        String cdsFormatFR = expr.toString(Syntax.CDS, java.util.Locale.FRENCH);

        assertEquals("0.1nm/s", cdsFormatDefault);
        assertEquals("0,1nm/s", cdsFormatFR);
    }

    @Test public void testCDSfactor() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.CDS, "3.162277x10+5m").getParsed();
        assertEquals("logFactor", 5.5, expr.getLogFactor(), 0.001);

        OneUnit u = expr.getUnit("m");
        assertEquals("Metre", u.getBaseUnitName());
    }

    @Test public void testSmallCDSfactor() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.CDS, "3.162277x10-5m").getParsed();
        assertEquals("logFactor", -4.5, expr.getLogFactor(), 0.001);

        OneUnit u = expr.getUnit("m");
        assertEquals("Metre", u.getBaseUnitName());
    }

    @Test(expected=UnitParserException.class) public void testCDSbadfactor() throws Exception {
        // same as above, but parsing a CDSfactor in a non-CDS syntax
        UnitExpr expr = new UnitParser(Syntax.FITS, "3.162277x10+5m").getParsed();
    }

    @Test(expected=UnitParserException.class) public void testNegativeCDSbadfactor() throws Exception {
        // same as above, but parsing a CDSfactor in a non-CDS syntax
        UnitExpr expr = new UnitParser(Syntax.FITS, "-3.162277x10+5m").getParsed();
    }

    @Test public void testLog() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.FITS, "log(MHz)").getParsed();
        assertEquals("log(MHz)", expr.toString(Syntax.FITS));
        assertEquals("\\si{\\text{\\ensuremath{\\log(\\si{\\mega Hz})}}}", expr.toString(Syntax.LATEX));
        assertEquals("[MHz]", expr.toString(Syntax.CDS));
    }

    @Test public void binaryUnits() throws Exception {
        // this is tested in bulk in the ParseAll/testunits.csv cases,
        // but here are some precise tests.
        UnitExpr expr = new UnitParser(Syntax.VOUNITS, "MB.Mibit").getParsed();

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition unitbyte = udm.lookupUnitDefinition(Syntax.VOUNITS, "byte");
        UnitDefinition unitB = udm.lookupUnitDefinition(Syntax.VOUNITS, "B");
        UnitDefinition bit = udm.lookupUnitDefinition(Syntax.VOUNITS, "bit");

        OneUnit us = expr.getUnit("byte");
        OneUnit u = expr.getUnit(unitbyte);
        assertSame(us, u);
        assertSame(unitbyte, u.getBaseUnitDefinition());
        assertSame(unitbyte, unitB); // two equivalent names
        
        us = expr.getUnit("bit");
        u = expr.getUnit(bit);
        assertSame(us, u);
        assertSame(bit, u.getBaseUnitDefinition());
    }

    private static void testUnitUsage(Syntax s, String exprString,
                                      boolean isRecognised, boolean satisfiesConstraints)
            throws Exception {
        UnitExpr expr = new UnitParser(s, exprString).getParsed();

        OneUnit u = expr.getUnit(0);
        String id = s.toString() + '{' + exprString + '}';
        if (isRecognised)
            assertTrue(id+"recognised", u.isRecognisedUnit(s));
        else
            assertFalse(id+"unrecognised", u.isRecognisedUnit(s));
        if (satisfiesConstraints)
            assertTrue(id+"satisfied", u.satisfiesUsageConstraints(s));
        else
            assertFalse(id+"unsatisfied", u.satisfiesUsageConstraints(s));
    }

    @Test public void testPrefixConstraints() throws Exception {
        // this is tested in bulk in the ParseAll/testunits.csv cases,
        // but here are some precise tests.

        // UnitExpr expr = new UnitParser(Syntax.VOUNITS, "Mm.Mis.MB.Mibit.Gfoo.Gibar").getParsed();

        // Byte is
        //   * recognised in all four syntaxes as 'byte', and
        //     additionally as 'B' in VOUNITS
        //   * may have an SI prefix in FITS, CDS and VOUNITS, but not OGIP.
        //   * may have a binary prefix in VOUNITS only
        testUnitUsage(Syntax.FITS,    "Mbyte", true, true);
        testUnitUsage(Syntax.OGIP,    "Mbyte", true, false);
        testUnitUsage(Syntax.CDS,     "Mbyte", true, true);
        testUnitUsage(Syntax.VOUNITS, "Mbyte", true, true);

        testUnitUsage(Syntax.VOUNITS, "MB", true, true);
        testUnitUsage(Syntax.VOUNITS, "Mibyte", true, true);
        testUnitUsage(Syntax.VOUNITS, "MiB", true, true);
        Syntax others[] = { Syntax.FITS, Syntax.OGIP, Syntax.CDS };
        for (Syntax stx : others) {
            testUnitUsage(stx, "MB", false, true); // 'B' is unrecognised
            testUnitUsage(stx, "MiB", false, true);    // ...so can have any prefixes
            // 'byte' is a recognised unit, but it's not listed as
            // permitting binary prefixes, in these three syntaxes,
            // so this will parse as the unit 'Mibyte'
            // (which has no constraints)
            testUnitUsage(stx, "Mibyte", false, true);
        }
        
        // Bit is
        //   * recognised in FITS, CDS and VOUNITS, but not OGIP
        //   * allowed SI prefixes in FITS and CDS
        //   * ...and both SI and binary prefixes in VOUNITS
        //   * ...but allowed binary prefixes only in VOUNITS
        testUnitUsage(Syntax.FITS,    "Mbit",  true, true);
        // binary prefix not recognised in FITS, so this is the unit 'Mibit'
        testUnitUsage(Syntax.FITS,    "Mibit", false, true);
        testUnitUsage(Syntax.OGIP,    "Mbit",  false, true);
        testUnitUsage(Syntax.OGIP,    "Mibit", false, true); // no binary prefix
        testUnitUsage(Syntax.CDS,     "Mbit",  true, true);
        testUnitUsage(Syntax.CDS,     "Mibit", false, true); // no binary prefix
        testUnitUsage(Syntax.VOUNITS, "Mbit",  true, true);
        testUnitUsage(Syntax.VOUNITS, "Mibit", true, true); // binary prefix OK

        // the unit 'foo' is unrecognised everywhere, and so can have any prefixes
        testUnitUsage(Syntax.FITS,    "Mfoo",  false, true);
        testUnitUsage(Syntax.FITS,    "Mifoo", false, true);
        testUnitUsage(Syntax.VOUNITS, "Mfoo",  false, true);
        testUnitUsage(Syntax.VOUNITS, "Mifoo", false, true);
    }
    
    @Test public void testMultifunctions() throws Exception {
        UnitExpr expr = new UnitParser(Syntax.FITS, "m.log(MHz).xfn(boink).log(boo).xfn(s)").getParsed();
        assertEquals("m log(MHz) xfn(boink) log(boo) xfn(s)", expr.toString(Syntax.FITS));
        assertEquals("m log(MHz) log(boo) xfn(s) xfn(boink)", expr.canonicalize().toString(Syntax.FITS));
        
        assertEquals("http://qudt.org/vocab/dimension#Dimension_SI_L", expr.getDimensions().getURI());

        UnitDefinitionMap udm = UnitDefinitionMap.getInstance();
        UnitDefinition metre = udm.lookupUnitDefinition(Syntax.FITS, "m");
        UnitDefinition second = udm.lookupUnitDefinition(Syntax.FITS, "s");
        UnitDefinition hertz  = udm.lookupUnitDefinition(Syntax.FITS, "Hz");

        OneUnit us = expr.getUnit("m");
        OneUnit u = expr.getUnit(metre);
        assertSame(us, u);
        assertSame(metre, u.getBaseUnitDefinition());

        FunctionDefinitionMap fdm = FunctionDefinitionMap.getInstance();
        FunctionDefinition logFn = fdm.lookupFunctionDefinition(Syntax.FITS, "log");

        // recommended function of recommended unit
        us = expr.getUnit("Hz");
        assertNotNull(us);
        u = expr.getUnit(hertz);
        assertEquals(u, us);
        assertEquals(hertz, u.getBaseUnitDefinition());
        assertEquals("Hertz", u.getBaseUnitName());
        assertTrue(u instanceof FunctionOfUnit);
        FunctionOfUnit fu = (FunctionOfUnit)u;
        assertEquals("log", fu.getFunctionName());
        assertEquals(logFn, fu.getFunctionDefinition());
        assertTrue(u.isRecognisedUnit(Syntax.FITS));
        assertTrue(u.isRecommendedUnit(Syntax.FITS));
        assertTrue(u.satisfiesUsageConstraints(Syntax.FITS));

        // unknown function of unknown unit
        u = expr.getUnit("boink");
        assertNotNull(u);
        assertEquals("boink", u.getBaseUnitName());
        assertTrue(u instanceof FunctionOfUnit);
        fu = (FunctionOfUnit)u;
        assertEquals("xfn", fu.getFunctionName());
        assertEquals(null, fu.getFunctionDefinition());
        assertFalse(u.isRecognisedUnit(Syntax.FITS));
        assertFalse(u.isRecommendedUnit(Syntax.FITS));
        assertTrue(u.satisfiesUsageConstraints(Syntax.FITS));

        // recommended function of unknown unit
        u = expr.getUnit("boo");
        assertNotNull(u);
        assertEquals("boo", u.getBaseUnitName());
        assertTrue(u instanceof FunctionOfUnit);
        fu = (FunctionOfUnit)u;
        assertEquals("log", fu.getFunctionName());
        assertEquals(logFn, fu.getFunctionDefinition());
        assertFalse(u.isRecognisedUnit(Syntax.FITS));
        assertFalse(u.isRecommendedUnit(Syntax.FITS));
        assertTrue(u.satisfiesUsageConstraints(Syntax.FITS));

        // unknown function of recommended unit
        us = expr.getUnit("s");
        u = expr.getUnit(second);
        assertEquals(u, us);
        assertEquals(second, u.getBaseUnitDefinition());
        assertEquals("Second", u.getBaseUnitName());
        assertTrue(u instanceof FunctionOfUnit);
        fu = (FunctionOfUnit)u;
        assertEquals("xfn", fu.getFunctionName());
        assertEquals(null, fu.getFunctionDefinition());
        assertFalse(u.isRecognisedUnit(Syntax.FITS));
        assertFalse(u.isRecommendedUnit(Syntax.FITS));
        assertTrue(u.satisfiesUsageConstraints(Syntax.FITS));
    }
    

    public static void main(String args[]) {
        org.junit.runner.JUnitCore.main("TestMisc");
    }
}
