package edu.northwestern.bioinformatics.studycalendar.utility.osgimosis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

/**
 * A membrane represents the boundary between the sets of classes loaded by
 * two separate classloaders.  It has a "near" side and a "far" side and manages
 * taking instances across the boundary in both directions.
 *
 * @author Rhett Sutphin
 */
@SuppressWarnings({ "RawUseOfParameterizedType" })
public class Membrane {
    private static final String DEPTH_MDC_KEY = "depth";
    private final Logger log = LoggerFactory.getLogger(getClass());

    private List<String> sharedPackages;
    private Map<Class, Encapsulator> encapsulators;

    private Cache cache;
    private ClassLoader nearClassLoader;
    private Map<String, Object[]> proxyConstructorParams;

    public Membrane(ClassLoader nearClassLoader, String... packages) {
        this.nearClassLoader = nearClassLoader;
        this.sharedPackages = Arrays.asList(packages);
        this.encapsulators = new IdentityHashMap<Class, Encapsulator>();
        this.cache = new Cache();
        this.proxyConstructorParams = new HashMap<String, Object[]>();
    }

    @SuppressWarnings({ "unchecked" })
    public Object farToNear(Object farObject) {
        return traverse(farObject, nearClassLoader);
    }

    @SuppressWarnings({ "unchecked" })
    public Object traverse(Object object, ClassLoader newCounterpartClassLoader) {
        pushMDC();
        try {
            log.debug("Traversing {} with {}", this, object);
            if (object == null) {
                log.trace(" - Null is null no matter where you're from");
                return null;
            }
            log.trace(" - Identity: {}@{}", object.getClass().getName(),
                Integer.toHexString(System.identityHashCode(object)));

            if (newCounterpartClassLoader == null) {
                log.debug(" - Not bridging object into bootstrap classloader");
                return object;
            }

            log.trace(" - Into {}", newCounterpartClassLoader);
            if (cache.get(object) == null) {
                Encapsulator encapsulator = getEncapsulator(object, newCounterpartClassLoader);
                if (encapsulator == null) {
                    log.debug(" - Not encapsulatable; returning original object");
                    return object;
                } else {
                    log.debug(" - Building new proxy");
                    cache.put(encapsulator.encapsulate(object), object);
                }
            } else {
                log.debug(" - Reusing cached value");
            }
            Object result = cache.get(object);
            log.trace(" - Complete with {}@{}", result.getClass().getName(),
                Integer.toHexString(System.identityHashCode(result)));
            return result;
        } finally {
            popMDC();
        }
    }

    public static void pushMDC() {
        String depth = MDC.get(DEPTH_MDC_KEY);
        if (depth == null) depth = "0";
        MDC.put(DEPTH_MDC_KEY, Integer.toString(Integer.parseInt(depth) + 1));
    }

    public static void popMDC() {
        String depth = MDC.get(DEPTH_MDC_KEY);
        if (depth == null) return;
        int newDepth = Integer.parseInt(depth) - 1;
        if (newDepth > 0) {
            MDC.put(DEPTH_MDC_KEY, Integer.toString(newDepth));
        } else {
            MDC.remove(DEPTH_MDC_KEY);
        }
    }

    public void registerProxyConstructorParameters(String className, Object[] parameters) {
        proxyConstructorParams.put(className, parameters);
    }

    private Encapsulator getEncapsulator(Object toEncapsulate, ClassLoader toEncapsulateFor) {
        if (!encapsulators.containsKey(toEncapsulate.getClass())) {
            encapsulators.put(toEncapsulate.getClass(), new DefaultEncapsulatorCreator(
                this, toEncapsulate.getClass(), toEncapsulateFor, proxyConstructorParams).create());
        }
        return encapsulators.get(toEncapsulate.getClass());
    }

    public Collection<String> getSharedPackages() {
        return sharedPackages;
    }

    @Override
    public String toString() {
        return new StringBuilder(getClass().getSimpleName()).
            append('@').
            append(Integer.toHexString(System.identityHashCode(this))).
            toString();
    }

    private static class Cache {
        private Map<Object, Object> pairs;

        private Cache() {
            pairs = new IdentityHashMap<Object, Object>();
        }

        public Object get(Object oneValue) {
            return pairs.get(oneValue);
        }

        public synchronized void put(Object one, Object two) {
            pairs.put(one, two);
            pairs.put(two, one);
        }
    }
}

@@ -201,16 +201,42 @@
 ection;%0A
+import java.util.HashMap;%0A
 import j
@@ -282,57 +282,8 @@
 til.
-List;%0Aimport java.util.Map;%0Aimport java.util.Hash
 Map;
@@ -750,20 +750,26 @@
 private 
-List
+Collection
 %3CString%3E
@@ -987,161 +987,11 @@
 ane(
-ClassLoader nearClassLoader, String... packages) %7B%0A        this.nearClassLoader = nearClassLoader;%0A        this.sharedPackages = Arrays.asList(packages);
+) %7B
 %0A   
@@ -1180,90 +1180,437 @@
     
-@SuppressWarnings(%7B %22unchecked%22 %7D)%0A    public Object farToNear(Object farObject) %7B
+public Membrane(ClassLoader nearClassLoader, String... packages) %7B%0A        this();%0A        this.nearClassLoader = nearClassLoader;%0A        this.sharedPackages = Arrays.asList(packages);%0A    %7D%0A%0A    @SuppressWarnings(%7B %22unchecked%22 %7D)%0A    public Object farToNear(Object farObject) %7B%0A        if (nearClassLoader == null) %7B%0A            throw new IllegalStateException(%22nearClassLoader must be set before calling farToNear%22);%0A        %7D
 %0A   
@@ -4387,32 +4387,303 @@
 lass());%0A    %7D%0A%0A
+    ////// CONFIGURATION%0A%0A    public void setNearClassLoader(ClassLoader nearClassLoader) %7B%0A        this.nearClassLoader = nearClassLoader;%0A    %7D%0A%0A    public void setSharedPackages(Collection%3CString%3E sharedPackages) %7B%0A        this.sharedPackages = sharedPackages;%0A    %7D%0A%0A
     public Colle
@@ -4752,24 +4752,51 @@
 ges;%0A    %7D%0A%0A
+    ////// OBJECT METHODS%0A%0A
     @Overrid

Allow Membrane to be configured via IoC property-style injection. #597.