package gizmoball.property;

import java.util.*;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Unit tests for property system
 *
 * @author Austin Clements
 * @version 1.0
 */
public class PropertyTest extends TestCase {
    private static final double TOLERANCE = 0.01;

    /**
     * A test class for Propertyified.  It has four properties:
     * Boolean, Integer, Double, and String, each of its respective
     * type (the first three are primitive types)
     *
     * <p>Integer is special.  If set to 13, an exception will be
     * raised.  If set to 42, an exception will be raised when
     * getInteger is called.
     *
     * <p>Additionally, this contains a get/set pair for Magic, which
     * is not exposed as a property.
     */
    private static class PropertyifiedClass extends Propertyified {
        private boolean booleanValue;
        private int integerValue;
        private double doubleValue;
        private String stringValue;

        public boolean getBoolean() {
            return booleanValue;
        }

        public void setBoolean(boolean v) {
            booleanValue = v;
            firePropertyObservers("Boolean", new Boolean(v));
        }

        public int getInteger() {
            if (integerValue == 42) {
                throw new RuntimeException("But what's the question?");
            }
            
            return integerValue;
        }

        public void setInteger(int v) {
            if (v == 13) {
                throw new RuntimeException("That would be unlucky!");
            }
            
            integerValue = v;
            firePropertyObservers("Integer", new Integer(v));
        }

        public double getDouble() {
            return doubleValue;
        }

        public void setDouble(double v) {
            doubleValue = v;
            firePropertyObservers("Double", new Double(v));
        }

        public String getString() {
            return stringValue;
        }

        public void setString(String v) {
            stringValue = v;
            firePropertyObservers("String", v);
        }

        public String getMagic() {
            return "Uh oh!";
        }

        public void setMagic(String value) {
        }

        protected void setUpProperties() {
            super.setUpProperties();
            addProperty("Boolean");
            addProperty("Integer");
            addProperty("Double");
            addProperty("String");
        }
    }

    public PropertyTest(String name) {
        super(name);
    }

    public static Test suite() {
        return new TestSuite(PropertyTest.class);
    }

    Propertyified pc;

    protected void setUp() {
        pc = new PropertyifiedClass();
    }

    /**
     * Test the Property class directly.  While this uses
     * PropertyifiedClass, it doesn't actually get the properties from
     * it, instead directly constructing Property's
     */
    public void testProperty() {
        // Booleans
        Property pBool = new Property(pc, "Bar", Boolean.TRUE, Boolean.class);

        assertSame(pc, pBool.getObject());
        assertEquals("Bar", pBool.getName());
        assertEquals(Boolean.TRUE, pBool.getValue());
        assertSame(Boolean.class, pBool.getType());
        assertEquals(true, pBool.getBooleanValue());
        try {
            pBool.getIntegerValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pBool.getDoubleValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pBool.getStringValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}

        // Integers
        Property pInt = new Property(pc, "Bar", new Integer(1), Integer.class);

        assertSame(pc, pInt.getObject());
        assertEquals("Bar", pInt.getName());
        assertEquals(new Integer(1), pInt.getValue());
        assertSame(Integer.class, pInt.getType());
        assertEquals(1, pInt.getIntegerValue());
        try {
            pInt.getBooleanValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pInt.getDoubleValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pInt.getStringValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}

        // Doubles
        Property pDouble =
            new Property(pc, "Bar", new Double(1.0), Double.class);

        assertSame(pc, pDouble.getObject());
        assertEquals("Bar", pDouble.getName());
        assertEquals(new Double(1.0), pDouble.getValue());
        assertSame(Double.class, pDouble.getType());
        assertEquals(1.0, pDouble.getDoubleValue(), TOLERANCE);
        try {
            pDouble.getBooleanValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pDouble.getIntegerValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pDouble.getStringValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}

        // Strings
        Property pString = new Property(pc, "Bar", "Foo!", String.class);

        assertSame(pc, pString.getObject());
        assertEquals("Bar", pString.getName());
        assertEquals("Foo!", pString.getValue());
        assertSame(String.class, pString.getType());
        assertEquals("Foo!", pString.getStringValue());
        try {
            pString.getBooleanValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pString.getIntegerValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pString.getDoubleValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}

        // General object
        Property pList = new Property(pc, "Bar", new ArrayList(),
                                      ArrayList.class);

        assertSame(pc, pList.getObject());
        assertEquals("Bar", pList.getName());
        assertEquals(new ArrayList(), pList.getValue());
        assertSame(ArrayList.class, pList.getType());
        try {
            pList.getBooleanValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pList.getIntegerValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pList.getDoubleValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
        try {
            pList.getStringValue();
            fail("Should have thrown UnsupportedOperationException");
        } catch (UnsupportedOperationException success) {}
    }

    private void getSetHelper(String name, Class type, Object[] values) {
        for (int i = 0; i < values.length; ++i) {
            Object value = values[i];
            
            pc.setProperty(name, value);
            Property p = pc.getProperty(name);
            assertSame(pc, p.getObject());
            assertEquals(name, p.getName());
            assertEquals(value, p.getValue());
            assertSame(type, p.getType());
        }
    }

    /**
     * Test directly getting and setting properties
     */
    public void testPropertyifiedGetSet() {
        // Test property list
        Collection properties = pc.getPropertyNames();
        assertTrue(properties.contains("Boolean"));
        assertTrue(properties.contains("Integer"));
        assertTrue(properties.contains("Double"));
        assertTrue(properties.contains("String"));
        
        // Test real properties
        getSetHelper("Boolean", Boolean.class,
                     new Boolean[] {new Boolean(true), new Boolean(false)});
        getSetHelper("Integer", Integer.class,
                     new Integer[] {new Integer(1), new Integer(20)});
        getSetHelper("Double", Double.class,
                     new Double[] {new Double(1.0), new Double(20.4)});
        getSetHelper("String", String.class,
                     new String[] {"Hi!", "Bye!"});

        // Try non-existent properties
        try {
            pc.getProperty("Magic");
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException success) {}
        try {
            pc.setProperty("Magic", "");
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException success) {}

        try {
            pc.getProperty("MoreMagic");
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException success) {}
        try {
            pc.setProperty("MoreMagic", "");
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException success) {}

        // Try get/set raising exceptions
        pc.setProperty("Integer", new Integer(42));
        try {
            pc.getProperty("Integer");
            fail("Should have thrown GetSetMethodException");
        } catch (GetSetMethodException iae) {
            assertTrue(iae.getCause() instanceof RuntimeException);
        }

        try {
            pc.setProperty("Integer", new Integer(13));
            fail("Should have thrown GetSetMethodException");
        } catch (GetSetMethodException iae) {
            assertTrue(iae.getCause() instanceof RuntimeException);
        }
    }

    /**
     * The last observed property.
     */
    Property observedProperty = null;

    /**
     * A general observer that simply sets {@link #observedProperty}
     * to whatever updates it receives
     */
    private class Observer implements PropertyObserver {
        public void propertyUpdate(Property newValue) {
            observedProperty = newValue;
        }
    }

    /**
     * Test the property observer system.
     */
    public void testPropertyifiedObservers() {
        PropertyObserver observer = new Observer();
        
        pc.setProperty("Integer", new Integer(1));
        assertNull(observedProperty);

        pc.addPropertyObserver(observer);

        pc.setProperty("Integer", new Integer(2));
        assertNotNull(observedProperty);
        assertSame(pc, observedProperty.getObject());
        assertEquals("Integer", observedProperty.getName());
        assertEquals(2, observedProperty.getIntegerValue());

        pc.setProperty("String", "Hi!");
        assertSame(pc, observedProperty.getObject());
        assertEquals("String", observedProperty.getName());
        assertEquals("Hi!", observedProperty.getStringValue());

        pc.removePropertyObserver(observer);
        pc.setProperty("Integer", new Integer(3));
        assertSame(pc, observedProperty.getObject());
        assertEquals("String", observedProperty.getName());
        assertEquals("Hi!", observedProperty.getStringValue());
    }
}
