/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.object;


import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;

import java.lang.reflect.Method;

import net.sf.jaxodraw.AbstractJaxoBaseTest;
import net.sf.jaxodraw.graph.JaxoGraph;
import net.sf.jaxodraw.gui.handle.JaxoDefaultHandle;
import net.sf.jaxodraw.io.JaxoIO;
import net.sf.jaxodraw.io.imports.JaxoImportAxodraw;
import net.sf.jaxodraw.util.JaxoColor;


/**
 * Base Test for JaxoObjects.
 */
public abstract class AbstractJaxoObjectTest extends AbstractJaxoBaseTest {
    private JaxoObject instance;
    private final JaxoHandle handle = new JaxoDefaultHandle();
    private JaxoGraph jaxo20OpenGraph;
    private JaxoGraph jaxo20ImportGraph;

    /**
     * Constructor.
     *
     * @param testName The name of this test.
     */
    protected AbstractJaxoObjectTest(String testName) {
        super(testName);
    }

    /** JUnit setup: get a new instance of the object. */
    @Override
    protected void setUp() {
        this.instance = getInstance();
    }

    /**
     * Returns a common instance of a default handle.
     *
     * @return a handle
     */
    protected JaxoHandle defaultHandle() {
        return handle;
    }

    private void init20OpenGraph() {
        JaxoIO io = new JaxoIO(null);
        jaxo20OpenGraph = io.open(getResourceFile("io/", "all-2.0.xml"), false);
    }

    private void init20ImportGraph() {
        JaxoImportAxodraw imp = new JaxoImportAxodraw();
        jaxo20ImportGraph =
                imp.importGraph(getResourceFile("io/", "all-2.0.tex"));
    }

    private JaxoObject instanceFromGraph(JaxoGraph graph, Class<?> clazz) {
        JaxoList<JaxoObject> list = graph.getObjectList();

        for (int j = 0; j < list.size(); j++) {
            JaxoObject ob = list.get(j);

            if (clazz.isInstance(ob)) {
                return ob;
            }
        }

        return null;
    }

    /**
     * Opens the test graph created with JaxoDraw-2.0 and returns the first
     * JaxoObject of the given class that is found in it.
     *
     * @param clazz the Class to look for.
     * @return A JaxoObject or null, if none was found.
     */
    protected JaxoObject instanceFrom20OpenGraph(Class<?> clazz) {
        if (jaxo20OpenGraph == null) {
            init20OpenGraph();
        }

        return instanceFromGraph(jaxo20OpenGraph, clazz);
    }

    /**
     * Imports the test graph created with JaxoDraw-2.0 and returns the first
     * JaxoObject of the given class that is found in it.
     *
     * @param clazz the Class to look for.
     * @return A JaxoObject or null, if none was found.
     */
    protected JaxoObject instanceFrom20ImportGraph(Class<?> clazz) {
        if (jaxo20ImportGraph == null) {
            init20ImportGraph();
        }

        return instanceFromGraph(jaxo20ImportGraph, clazz);
    }

    /**
     * Asserts that the point (x,y) corresponds to the expected handle.
     *
     * @param x x coordinate of the point to check.
     * @param y y coordinate of the point to check.
     * @param expected the expected handle.
     */
    protected void checkHandle(int x, int y, int expected) {
        int actual = instance.getGrabbedHandle(x, y, handle);
        assertEquals("Wrong handle!", expected, actual);
    }

    /** Tests isAroundP1. */
    public void testIsAroundP1() {
        instance.setLocation(111, 123);

        assertTrue("Not around P1!",
                instance.isAround(JaxoObject.SELECT_P1, 111, 123, handle));
        assertFalse("Is around P1!",
                instance.isAround(JaxoObject.SELECT_P1, 150, 100, handle));
    }

    /** Tests getName. */
    public void testName() {
        assertEquals(className(), getInstance().getName());
    }

    /** Test the serializable properties of a JaxoObject. */
    public void testBeanProperties() {
        BeanInfo bi = null;

        try {
            bi = Introspector.getBeanInfo(getInstance().getClass());
        } catch (IntrospectionException e) {
            e.printStackTrace();
            fail(e.getMessage());
        }

        PropertyDescriptor[] pds = bi.getPropertyDescriptors();
        String[] tmp = new String[pds.length];

        Method readMethod = null;
        Method writeMethod = null;

        int num = 0;

        for (int i = 0; i < pds.length; i++) {
            readMethod = pds[i].getReadMethod();
            writeMethod = pds[i].getWriteMethod();

            boolean isTransient = false;
            Boolean trans = (Boolean) pds[i].getValue("transient");

            if (trans != null) {
                isTransient = trans.booleanValue();
            }

            if (readMethod != null && writeMethod != null && !isTransient) {
                tmp[num] = pds[i].getName();
                num++;
            }
        }

        String[] transients = new String[num];
        System.arraycopy(tmp, 0, transients, 0, num);

        assertEquivalentArrays(serializableProperties(), transients);
    }

    /**
     * Test PropertyChangeSupport of JaxoObject.
     */
    public void testPropertyChangeListener() {
        PropertyChangeRecorder recorder = new PropertyChangeRecorder();

        JaxoObject ob = getInstance();
        ob.addPropertyChangeListener(recorder);
        assertNull(recorder.getName());
        ob.setColor(JaxoColor.APRICOT);
        assertEquals("color", recorder.getName());
        assertEquals(JaxoColor.APRICOT, recorder.getNewValue());

        recorder.reset();

        ob.removePropertyChangeListener(recorder);
        ob.setColor(JaxoColor.AQUAMARINE);
        assertNull(recorder.getName());
        assertNull(recorder.getNewValue());
    }

      //
     // abstract tests
    //

    // public abstract void testPaint();

    // public abstract void testPaintHandles();

    /** Tests canBeSelected. */
    public abstract void testCanBeSelected();

    /** Tests latexCommand. */
    public abstract void testLatexCommand();

    /** Tests latexWidth. */
    public abstract void testLatexWidth();

    /** Tests rescaleObject. */
    public abstract void testRescaleObject();

    /** Tests getBounds. */
    public abstract void testGetBounds();

    /** Tests the axodraw inmport. */
    public abstract void testAxodraw();

    /** Tests createNewObject in JaxoObjectFactory. */
    public abstract void testCreateNewObject();

    /** Tests smallestDistanceTo. */
    public abstract void testSmallestDistanceTo();

    /** Tests getGrabbedHandle. */
    public abstract void testGetGrabbedHandle();

    /** Tests moveBy. */
    public abstract void testMoveBy();

    /** Tests copyFrom and isCopy. */
    public abstract void testCopy();

    /** Tests JaxoDraw-2.0 compatibility. */
    public abstract void testJaxo20Compat();

    /**
     * Used to test getName.
     *
     * @return the name of the JaxoObject.
     */
    public abstract String className();

    /**
     * Returns the names of properties that this object should serialize.
     *
     * @return the property names in arbitrary order.
     */
    public abstract String[] serializableProperties();

      //
     // abstract instance
    //

    /**
     * Return an instance of a JaxoObject.
     * Used to test methods in abstract super classes.
     *
     * @return an instance of a JaxoObject.
     */
    public abstract JaxoObject getInstance();

    private class PropertyChangeRecorder implements PropertyChangeListener {

        private String name;
        private Object newValue;

        public void propertyChange(PropertyChangeEvent evt) {
            this.name = evt.getPropertyName();
            this.newValue = evt.getNewValue();
        }

        public void reset() {
            this.name = null;
            this.newValue = null;
        }

        public String getName() {
            return name;
        }

        public Object getNewValue() {
            return newValue;
        }
    }
}
