/*
 * Copyright (C) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package libcore.java.util.beans;

import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeListenerProxy;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventListener;
import java.util.List;
import junit.framework.TestCase;
import libcore.libcore.util.SerializationTester;

public final class PropertyChangeSupportTest extends TestCase {

    public void testOldAndNewAreBothNull() {
        Object bean = new Object();
        PropertyChangeSupport support = new PropertyChangeSupport(bean);
        EventLog listener = new EventLog();
        support.addPropertyChangeListener(listener);

        PropertyChangeEvent nullToNull = new PropertyChangeEvent(bean, "a", null, null);
        support.firePropertyChange(nullToNull);
        assertEquals(Arrays.<PropertyChangeEvent>asList(nullToNull), listener.log);
    }

    public void testOldAndNewAreTheSame() {
        Object bean = new Object();
        PropertyChangeSupport support = new PropertyChangeSupport(bean);
        EventLog listener = new EventLog();
        support.addPropertyChangeListener(listener);

        PropertyChangeEvent xToX = new PropertyChangeEvent(bean, "a", "x", new String("x"));
        support.firePropertyChange(xToX);
        assertEquals(Arrays.<PropertyChangeEvent>asList(), listener.log);
    }

    public void testEventsFilteredByProxies() {
        Object bean = new Object();
        PropertyChangeEvent eventA = new PropertyChangeEvent(bean, "a", false, true);
        PropertyChangeEvent eventB = new PropertyChangeEvent(bean, "b", false, true);
        PropertyChangeEvent eventC = new PropertyChangeEvent(bean, "c", false, true);

        PropertyChangeSupport support = new PropertyChangeSupport(bean);

        EventLog all = new EventLog();
        support.addPropertyChangeListener(all);

        EventLog proxiedA = new EventLog();
        support.addPropertyChangeListener(new PropertyChangeListenerProxy("a", proxiedA));

        EventLog addA = new EventLog();
        support.addPropertyChangeListener("a", addA);

        EventLog addAProxiedB = new EventLog();
        support.addPropertyChangeListener("a", new PropertyChangeListenerProxy("b", addAProxiedB));

        EventLog proxiedAB = new EventLog();
        support.addPropertyChangeListener(new PropertyChangeListenerProxy(
                "a", new PropertyChangeListenerProxy("b", proxiedAB)));

        EventLog proxiedAA = new EventLog();
        support.addPropertyChangeListener(new PropertyChangeListenerProxy("a",
                new PropertyChangeListenerProxy("a", proxiedAA)));

        EventLog proxiedAAC = new EventLog();
        support.addPropertyChangeListener(new PropertyChangeListenerProxy("a",
                new PropertyChangeListenerProxy("a",
                new PropertyChangeListenerProxy("c", proxiedAAC))));

        support.firePropertyChange(eventA);
        support.firePropertyChange(eventB);
        support.firePropertyChange(eventC);

        assertEquals(Arrays.asList(eventA, eventB, eventC), all.log);
        assertEquals(Arrays.asList(eventA), proxiedA.log);
        assertEquals(Arrays.asList(eventA), addA.log);
        assertEquals(Arrays.asList(eventA), addAProxiedB.log);
        assertEquals(Arrays.asList(eventA), proxiedAB.log);
        assertEquals(Arrays.asList(eventA), proxiedAA.log);
        assertEquals(Arrays.asList(eventA), proxiedAAC.log);
    }

    /**
     * Test that we need to do our own equals() work to manually unwrap an
     * arbitrary number of proxies.
     */
    public void testRemoveWithProxies() {
        Object bean = new Object();
        PropertyChangeSupport support = new PropertyChangeSupport(bean);

        EventLog all = new EventLog();
        support.addPropertyChangeListener(all);
        assertEquals(1, support.getPropertyChangeListeners().length);

        EventLog proxiedA = new EventLog();
        support.addPropertyChangeListener(new PropertyChangeListenerProxy("a", proxiedA));
        assertEquals(2, support.getPropertyChangeListeners().length);

        EventLog addA = new EventLog();
        support.addPropertyChangeListener("a", addA);
        assertEquals(3, support.getPropertyChangeListeners().length);

        EventLog addAProxiedB = new EventLog();
        support.addPropertyChangeListener("a", new PropertyChangeListenerProxy("b", addAProxiedB));
        assertEquals(4, support.getPropertyChangeListeners().length);

        EventLog proxiedAB = new EventLog();
        PropertyChangeListenerProxy proxyAB = new PropertyChangeListenerProxy(
                "a", new PropertyChangeListenerProxy("b", proxiedAB));
        support.addPropertyChangeListener(proxyAB);
        assertEquals(5, support.getPropertyChangeListeners().length);

        EventLog proxiedAAC = new EventLog();
        support.addPropertyChangeListener(new PropertyChangeListenerProxy("a",
                new PropertyChangeListenerProxy("a",
                        new PropertyChangeListenerProxy("c", proxiedAAC))));
        assertEquals(6, support.getPropertyChangeListeners().length);

        support.removePropertyChangeListener(all);
        assertEquals(5, support.getPropertyChangeListeners().length);
        support.removePropertyChangeListener("a", proxiedA);
        assertEquals(4, support.getPropertyChangeListeners().length);
        support.removePropertyChangeListener(new PropertyChangeListenerProxy("a", addA));
        assertEquals(3, support.getPropertyChangeListeners().length);
        support.removePropertyChangeListener(
                "a", new PropertyChangeListenerProxy("b", addAProxiedB));
        assertEquals(2, support.getPropertyChangeListeners().length);
        support.removePropertyChangeListener(proxyAB);
        assertEquals(1, support.getPropertyChangeListeners().length);

        support.removePropertyChangeListener(proxiedAAC);
        assertEquals(1, support.getPropertyChangeListeners().length);
        support.removePropertyChangeListener(new PropertyChangeListenerProxy("a", proxiedAAC));
        assertEquals(0, support.getPropertyChangeListeners().length);
    }

    public void testAddingOneListenerTwice() {
        Object bean = new Object();
        PropertyChangeSupport support = new PropertyChangeSupport(bean);
        EventLog log = new EventLog();
        support.addPropertyChangeListener("a", log);
        support.addPropertyChangeListener(log);
        support.addPropertyChangeListener(log);
        support.addPropertyChangeListener("a", log);

        PropertyChangeEvent eventA = new PropertyChangeEvent(bean, "a", false, true);
        PropertyChangeEvent eventB = new PropertyChangeEvent(bean, "b", false, true);

        support.firePropertyChange(eventA);
        support.firePropertyChange(eventB);

        assertEquals(Arrays.asList(eventA, eventA, eventA, eventA, eventB, eventB), log.log);
    }

    public void testAddingAListenerActuallyAddsAProxy() {
        Object bean = new Object();
        PropertyChangeSupport support = new PropertyChangeSupport(bean);
        PropertyChangeListener listener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {}
        };
        support.addPropertyChangeListener("a", listener);

        PropertyChangeListenerProxy p1
                = (PropertyChangeListenerProxy) support.getPropertyChangeListeners()[0];
        assertEquals(PropertyChangeListenerProxy.class, p1.getClass());
        assertTrue(p1 != listener); // weird but consistent with the RI
        assertEquals("a", p1.getPropertyName());
        assertEquals(listener, p1.getListener());
    }

    public void testAddingAProxy() {
        Object bean = new Object();
        PropertyChangeSupport support = new PropertyChangeSupport(bean);
        PropertyChangeListener listener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {}
        };
        PropertyChangeListenerProxy proxy = new PropertyChangeListenerProxy("a", listener);
        support.addPropertyChangeListener("b", proxy);

        // this proxy sets us up to receive 'b' events
        PropertyChangeListenerProxy p1
                = (PropertyChangeListenerProxy) support.getPropertyChangeListeners()[0];
        assertEquals(PropertyChangeListenerProxy.class, p1.getClass());
        assertEquals("b", p1.getPropertyName());

        // there's no more proxy on 'a'.
        assertEquals(listener, p1.getListener());
    }

    public void testSerialize() {
        String s = "aced0005737200206a6176612e6265616e732e50726f70657274794368616e67"
                + "65537570706f727458d5d264574860bb03000349002a70726f706572747943686"
                + "16e6765537570706f727453657269616c697a65644461746156657273696f6e4c"
                + "00086368696c6472656e7400154c6a6176612f7574696c2f486173687461626c6"
                + "53b4c0006736f757263657400124c6a6176612f6c616e672f4f626a6563743b78"
                + "7000000002737200136a6176612e7574696c2e486173687461626c6513bb0f252"
                + "14ae4b803000246000a6c6f6164466163746f724900097468726573686f6c6478"
                + "703f4000000000000877080000000b00000001740001617371007e00000000000"
                + "2707400046265616e7372003a6c6962636f72652e6a6176612e7574696c2e6265"
                + "616e732e50726f70657274794368616e6765537570706f7274546573742445766"
                + "56e744c6f67b92667637d0b6f450200024c00036c6f677400104c6a6176612f75"
                + "74696c2f4c6973743b4c00046e616d657400124c6a6176612f6c616e672f53747"
                + "2696e673b7870737200136a6176612e7574696c2e41727261794c6973747881d2"
                + "1d99c7619d03000149000473697a6578700000000077040000000a7874000b6c6"
                + "97374656e6572546f4171007e000c70787871007e00087371007e00097371007e"
                + "000d0000000077040000000a7874000d6c697374656e6572546f416c6c7078";

        Object bean = "bean";
        PropertyChangeSupport support = new PropertyChangeSupport(bean);
        EventLog listenerToAll = new EventLog();
        listenerToAll.name = "listenerToAll";
        EventLog listenerToA = new EventLog();
        listenerToA.name = "listenerToA";
        support.addPropertyChangeListener(listenerToAll);
        support.addPropertyChangeListener("a", listenerToA);
        support.addPropertyChangeListener("a", listenerToA);

        new SerializationTester<PropertyChangeSupport>(support, s) {
            @Override protected boolean equals(PropertyChangeSupport a, PropertyChangeSupport b) {
                return describe(a.getPropertyChangeListeners())
                        .equals(describe(b.getPropertyChangeListeners()));
            }
            @Override protected void verify(PropertyChangeSupport deserialized) {
                assertEquals("[a to listenerToA, a to listenerToA, listenerToAll]",
                        describe(deserialized.getPropertyChangeListeners()));
            }
        }.test();
    }

    public void testFireIndexedPropertyChange_intUpdate() {
        Object bean = "bean";
        PropertyChangeSupport support = new PropertyChangeSupport(bean);

        EventLog listenerToIntProperty = new EventLog();
        support.addPropertyChangeListener("intProperty", listenerToIntProperty);

        support.fireIndexedPropertyChange("intProperty", 10, 1, 2);

        List<PropertyChangeEvent> events = listenerToIntProperty.log;
        assertEquals(1, events.size());
        assertEquals(1, events.get(0).getOldValue());
        assertEquals(2, events.get(0).getNewValue());
        assertEquals("intProperty", events.get(0).getPropertyName());
        assertTrue(events.get(0) instanceof IndexedPropertyChangeEvent);
        assertEquals(10, ((IndexedPropertyChangeEvent) events.get(0)).getIndex());
    }

    public void testFireIndexedPropertyChange_intUpdate_noEventWhenOldIsEqualToNew() {
        Object bean = "bean";
        PropertyChangeSupport support = new PropertyChangeSupport(bean);

        EventLog listenerToIntProperty = new EventLog();
        support.addPropertyChangeListener("intProperty", listenerToIntProperty);

        support.fireIndexedPropertyChange("intProperty", 0, 1, 1);

        List<PropertyChangeEvent> events = listenerToIntProperty.log;
        assertTrue(events.isEmpty());
    }

    public void testFireIndexedPropertyChange_booleanUpdate() {
        Object bean = "bean";
        PropertyChangeSupport support = new PropertyChangeSupport(bean);

        EventLog listenerToBooleanProperty = new EventLog();
        support.addPropertyChangeListener("booleanProperty", listenerToBooleanProperty);

        support.fireIndexedPropertyChange("booleanProperty", 11, true, false);

        List<PropertyChangeEvent> events = listenerToBooleanProperty.log;
        assertEquals(1, events.size());
        assertEquals(true, events.get(0).getOldValue());
        assertEquals(false, events.get(0).getNewValue());
        assertEquals("booleanProperty", events.get(0).getPropertyName());
        assertTrue(events.get(0) instanceof IndexedPropertyChangeEvent);
        assertEquals(11, ((IndexedPropertyChangeEvent) events.get(0)).getIndex());
    }

    public void testFireIndexedPropertyChange_booleanUpdate_noEventWhenOldIsEqualToNew() {
        Object bean = "bean";
        PropertyChangeSupport support = new PropertyChangeSupport(bean);

        EventLog listenerToBooleanProperty = new EventLog();
        support.addPropertyChangeListener("booleanProperty", listenerToBooleanProperty);

        support.fireIndexedPropertyChange("booleanProperty", 0, true, true);

        List<PropertyChangeEvent> events = listenerToBooleanProperty.log;
        assertTrue(events.isEmpty());
    }

    private String describe(PropertyChangeListener[] listeners) {
        List<String> result = new ArrayList<String>();
        for (PropertyChangeListener listener : listeners) {
            result.add(describe(listener));
        }
        Collections.sort(result);
        return result.toString();
    }

    private String describe(EventListener listener) {
        if (listener instanceof PropertyChangeListenerProxy) {
            PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
            return proxy.getPropertyName() + " to " + describe(proxy.getListener());
        } else {
            return listener.toString();
        }
    }

    static class EventLog implements PropertyChangeListener, Serializable {
        String name = "EventLog";
        List<PropertyChangeEvent> log = new ArrayList<PropertyChangeEvent>();
        public void propertyChange(PropertyChangeEvent event) {
            log.add(event);
        }
        @Override public String toString() {
            return name;
        }
    }
}
