package org.picocontainer.gems.adapters;

import com.thoughtworks.proxy.factory.CglibProxyFactory;
import com.thoughtworks.proxy.toys.hotswap.Swappable;

import org.picocontainer.MutablePicoContainer;
import org.picocontainer.defaults.CachingComponentAdapter;
import org.picocontainer.defaults.CachingComponentAdapterFactory;
import org.picocontainer.defaults.ComponentAdapterFactory;
import org.picocontainer.defaults.ConstructorInjectionComponentAdapter;
import org.picocontainer.defaults.ConstructorInjectionComponentAdapterFactory;
import org.picocontainer.defaults.DefaultComponentAdapterFactory;
import org.picocontainer.defaults.DefaultPicoContainer;
import org.picocontainer.tck.AbstractComponentAdapterFactoryTestCase;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


public class HotSwappingComponentAdapterFactoryTestCase extends AbstractComponentAdapterFactoryTestCase {
    private HotSwappingComponentAdapterFactory implementationHidingComponentAdapterFactory = new HotSwappingComponentAdapterFactory(
            new DefaultComponentAdapterFactory());
    private CachingComponentAdapterFactory cachingComponentAdapterFactory = new CachingComponentAdapterFactory(
            implementationHidingComponentAdapterFactory);

    public void testComponentRegisteredWithInterfaceKeyOnlyImplementsThatInterfaceUsingStandardProxyfactory() {
        DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwappingComponentAdapterFactory(
                new ConstructorInjectionComponentAdapterFactory()));
        pico.registerComponentImplementation(Collection.class, ArrayList.class);
        Object collection = pico.getComponentInstance(Collection.class);
        assertTrue(collection instanceof Collection);
        assertFalse(collection instanceof List);
        assertFalse(collection instanceof ArrayList);
    }

    public void testComponentRegisteredWithOtherKeyImplementsAllInterfacesUsingStandardProxyFactory() {
        DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwappingComponentAdapterFactory(
                new ConstructorInjectionComponentAdapterFactory()));
        pico.registerComponentImplementation("list", ArrayList.class);
        Object collection = pico.getComponentInstance("list");
        assertTrue(collection instanceof List);
        assertFalse(collection instanceof ArrayList);
    }

    public void testComponentRegisteredWithInterfaceKeyOnlyImplementsThatInterfaceUsingCGLIBProxyfactory() {
        DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwappingComponentAdapterFactory(
                new ConstructorInjectionComponentAdapterFactory(), new CglibProxyFactory()));
        pico.registerComponentImplementation(Collection.class, ArrayList.class);
        Object collection = pico.getComponentInstance(Collection.class);
        assertTrue(collection instanceof Collection);
        assertFalse(collection instanceof List);
        assertFalse(collection instanceof ArrayList);
    }

    public void testComponentRegisteredWithOtherKeyImplementsAllInterfacesUsingCGLIBProxyFactory() {
        DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwappingComponentAdapterFactory(
                new ConstructorInjectionComponentAdapterFactory(), new CglibProxyFactory()));
        pico.registerComponentImplementation("list", ArrayList.class);
        Object collection = pico.getComponentInstance("list");
        assertTrue(collection instanceof Collection);
        assertTrue(collection instanceof List);
        assertTrue(collection instanceof ArrayList);
        assertTrue(collection.getClass().getSuperclass().equals(ArrayList.class));
    }

    // START SNIPPET: man
    public static interface Man {
        Woman getWoman();

        void kiss();

        boolean wasKissed();
    }

    // END SNIPPET: man

    // START SNIPPET: woman
    public static interface Woman {
        Man getMan();
    }

    // END SNIPPET: woman

    public static interface SuperWoman extends Woman {
    }

    public static class Husband implements Man {
        public final Woman partner;
        private boolean wasKissed;

        public Husband(Woman partner) {
            this.partner = partner;
        }

        public Woman getWoman() {
            return partner;
        }

        public void kiss() {
            wasKissed = true;
        }

        public boolean wasKissed() {
            return wasKissed;
        }
    }

    public static class Wife implements SuperWoman {
        public final Man partner;

        public Wife(Man partner) {
            this.partner = partner;
        }

        public Man getMan() {
            return partner;
        }
    }

    public void testLowLevelCheating() {
        ComponentAdapterFactory caf = createComponentAdapterFactory();
        DefaultPicoContainer pico = new DefaultPicoContainer(caf);

        CachingComponentAdapter wifeAdapter = (CachingComponentAdapter)caf.createComponentAdapter("wife", Wife.class, null);
        CachingComponentAdapter husbandAdapter = (CachingComponentAdapter)caf
                .createComponentAdapter("husband", Husband.class, null);

        pico.registerComponent(wifeAdapter);
        pico.registerComponent(husbandAdapter);

        Woman wife = (Woman)wifeAdapter.getComponentInstance(pico);
        Man wifesMan = wife.getMan();
        wifesMan.kiss();
        Man man = (Man)husbandAdapter.getComponentInstance(pico);
        assertTrue(man.wasKissed());

        assertSame(man, wife.getMan());
        assertSame(wife, man.getWoman());

        // Let the wife use another (single) man
        Man newMan = new Husband(null);
        Man oldMan = (Man)((Swappable)man).hotswap(newMan);

        wife.getMan().kiss();
        assertTrue(newMan.wasKissed());
        assertNotSame(man, oldMan);
        assertNotSame(oldMan, newMan);
        assertNotSame(newMan, man);

        assertFalse(man.hashCode() == oldMan.hashCode());
        assertFalse(oldMan.hashCode() == newMan.hashCode());
        // TODO: Enable for proxytoys-0.2 final
        // assertTrue(newMan.hashCode() == man.hashCode());
    }


    public void testTheThingThatPaulDoesNotLikeAboutTheCurrentHotSwapper() {
        DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwappingComponentAdapterFactory());

        HotSwappingComponentAdapter ca = (HotSwappingComponentAdapter) pico.registerComponentImplementation(List.class, ArrayList.class);

        List list = (List) pico.getComponentInstanceOfType(List.class);

        // I do not like that I can cast the object to something that is mutable beyond the scope
        // of its design (list functions)
        List list2 = (List) ((Swappable) list).hotswap(new ArrayList());

        MutablePicoContainer child = pico.makeChildContainer();
        child.registerComponentImplementation(ExposesListMember.class, ExposesListMemberImpl.class);
        Object componentInstance = child.getComponentInstanceOfType(ExposesListMember.class);
        ExposesListMember exposer = (ExposesListMember) componentInstance;

        // still mutable for children :-(
        List list3 = (List) ((Swappable) exposer.getList()).hotswap(new ArrayList());

        // what I really want ...
        //ca.swapInstance(...);
    }

    public static interface ExposesListMember {
        List getList();
    }

    public static class ExposesListMemberImpl implements ExposesListMember {
        private final List list;

        public ExposesListMemberImpl(List l) {
            list = l;
        }

        public List getList() {
            return list;
        }

    }


    public void testHighLevelCheating() {
        MutablePicoContainer pico = new DefaultPicoContainer(createComponentAdapterFactory());

        // Register two classes with mutual dependencies in the constructor (!!!)
        pico.registerComponentImplementation(Wife.class);
        pico.registerComponentImplementation(Husband.class);

        Woman wife = (Woman)pico.getComponentInstance(Wife.class);
        Man man = (Man)pico.getComponentInstance(Husband.class);

        assertSame(man, wife.getMan());
        assertSame(wife, man.getWoman());

        // Let the wife use another (single) man
        Man newMan = new Husband(null);
        Man oldMan = (Man)((Swappable)man).hotswap(newMan);

        wife.getMan().kiss();
        assertFalse(oldMan.wasKissed());
        assertTrue(newMan.wasKissed());

    }

    public void testBigamy() {
        DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwappingComponentAdapterFactory(
                new ConstructorInjectionComponentAdapterFactory()));
        pico.registerComponentImplementation(Woman.class, Wife.class);
        Woman firstWife = (Woman)pico.getComponentInstance(Woman.class);
        Woman secondWife = (Woman)pico.getComponentInstance(Woman.class);
        assertNotSame(firstWife, secondWife);

    }

    public static class Bad implements Serializable {
        public Bad() {
            throw new IllegalStateException("HAHA");
        }
    }

    public void testIHCAFwithCTORandNoCaching() {
        // http://lists.codehaus.org/pipermail/picocontainer-dev/2004-January/001985.html
        MutablePicoContainer pico = new DefaultPicoContainer();
        pico.registerComponent(new HotSwappingComponentAdapter(new ConstructorInjectionComponentAdapter("l", ArrayList.class)));

        List list1 = (List)pico.getComponentInstance("l");
        List list2 = (List)pico.getComponentInstance("l");

        assertNotSame(list1, list2);
        assertFalse(list1 instanceof ArrayList);

        list1.add("Hello");
        assertTrue(list1.contains("Hello"));
        assertFalse(list2.contains("Hello"));
    }

    public void testSwappingViaSwappableInterface() {
        MutablePicoContainer pico = new DefaultPicoContainer();
        pico.registerComponent(new HotSwappingComponentAdapter(new ConstructorInjectionComponentAdapter("l", ArrayList.class)));
        List l = (List)pico.getComponentInstance("l");
        l.add("Hello");
        final ArrayList newList = new ArrayList();
        ArrayList oldSubject = (ArrayList)((Swappable)l).hotswap(newList);
        assertEquals("Hello", oldSubject.get(0));
        assertTrue(l.isEmpty());
        l.add("World");
        assertEquals("World", l.get(0));
    }

    public interface OtherSwappable {
        Object hotswap(Object newSubject);
    }

    public static class OtherSwappableImpl implements OtherSwappable {
        public Object hotswap(Object newSubject) {
            return "TADA";
        }
    }

    public void testInterferingSwapMethodsInComponentMasksHotSwappingFunctionality() {
        MutablePicoContainer pico = new DefaultPicoContainer();
        pico.registerComponent(new HotSwappingComponentAdapter(new ConstructorInjectionComponentAdapter(
                "os", OtherSwappableImpl.class)));
        OtherSwappable os = (OtherSwappable)pico.getComponentInstance("os");
        OtherSwappable os2 = new OtherSwappableImpl();

        assertEquals("TADA", os.hotswap(os2));
        Swappable os_ = (Swappable)os;
        assertEquals("TADA", os_.hotswap(os2));
    }

    protected ComponentAdapterFactory createComponentAdapterFactory() {
        return cachingComponentAdapterFactory;
    }

    public static class Yin {
        private final Yang yang;

        public Yin(Yang yang) {
            this.yang = yang;
        }

        public Yang getYang() {
            return yang;
        }
    }

    public static class Yang {
        private final Yin yin;

        public Yang(Yin yin) {
            this.yin = yin;
        }

        public Yin getYin() {
            return yin;
        }
    }

    // TODO: Fails with versions of cglib >= 2.0.1
    public void testShouldBeAbleToHandleMutualDependenciesWithoutInterfaceImplSeparation() {
        MutablePicoContainer pico = new DefaultPicoContainer(new CachingComponentAdapterFactory(
                new HotSwappingComponentAdapterFactory(new ConstructorInjectionComponentAdapterFactory(), new CglibProxyFactory())));

        pico.registerComponentImplementation(Yin.class);
        pico.registerComponentImplementation(Yang.class);

        Yin yin = (Yin)pico.getComponentInstance(Yin.class);
        Yang yang = (Yang)pico.getComponentInstance(Yang.class);

        assertSame(yin, yang.getYin());
        assertSame(yang, yin.getYang());
    }

}
