/************************************************
 * Copyright (c) Shen Li. All rights reserved.  *
 * http://joyaop.sourceforge.net                *
 * -------------------------------------------- *
 * Distributable under LGPL license.            *
 * See terms of license at gnu.org.             *
 ************************************************/
package net.sf.joyaop.impl;

import net.sf.joyaop.framework.AspectFactory;
import net.sf.joyaop.framework.RuntimeAspect;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Shen Li
 */
class RuntimeAspectInstance implements Serializable {
    private static final long serialVersionUID = 0;
    private static AspectFactory aspectFactory = new DefaultAspectFactory();
    private ObjectReference implementationReference;
    private Map parameters;

    public static AspectFactory getAspectFactory() {
        return aspectFactory;
    }

    public static void setAspectFactory(AspectFactory aspectFactory) {
        RuntimeAspectInstance.aspectFactory = aspectFactory;
    }

    public RuntimeAspectInstance(RuntimeAspect aspect, Class originalClass) {
        this.parameters = aspect.getParameters();
        this.implementationReference = createImplementationReference(aspect, originalClass);
    }

    public Object getParameter(String name) {
        if (parameters == null) {
            return null;
        }
        return parameters.get(name);
    }

    public Object getImplementation() {
        return implementationReference.get();
    }

    public ObjectReference createImplementationReference(RuntimeAspect aspect, Class originalClass) {
        if (RuntimeAspect.JVM_SCOPE.equals(aspect.getScope())) {
            return JvmScopeObjectReference.getInstance(aspect.getAspectClass());
        } else if (RuntimeAspect.CLASS_SCOPE.equals(aspect.getScope())) {
            return ClassScopeObjectReference.getInstance(aspect.getAspectClass(), originalClass);
        } else if (RuntimeAspect.INSTANCE_SCOPE.equals(aspect.getScope())) {
            return new InstanceScopeObjectReference(aspect.getAspectClass());
        } else if (RuntimeAspect.THREAD_SCOPE.equals(aspect.getScope())) {
            return ThreadScopeObjectReference.getInstance(aspect.getAspectClass());
        } else {
            throw new IllegalArgumentException("The aspect has an unknown scope value: " + aspect.getScope());
        }
    }

    private static Object createImplementation(Class implementationClass) {
        return aspectFactory.newInstance(implementationClass);
    }

    // TODO serialization
    private abstract static class ObjectReference implements Serializable {
        protected Class implementationClass;

        protected ObjectReference(Class implementationClass) {
            this.implementationClass = implementationClass;
        }

        public abstract void set(Object implementation);

        public abstract Object get();
    }

    private abstract static class LazyLoadObjectReference extends ObjectReference {
        protected Object implementation;

        protected LazyLoadObjectReference(Class implementationClass) {
            super(implementationClass);
        }

        public synchronized Object get() {
            if (implementation == null) {
                implementation = createImplementation(implementationClass);
            }
            return implementation;
        }

        public void set(Object implementation) {
            this.implementation = implementation;
        }

        private void writeObject(ObjectOutputStream out) throws IOException {
            out.writeObject(implementationClass);
            if (Serializable.class.isAssignableFrom(implementationClass)) {
                out.writeObject(implementation);
            } else {
                out.writeObject(null);
            }
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            implementationClass = (Class) in.readObject();
            implementation = in.readObject();
        }
    }

    private static class JvmScopeObjectReference extends LazyLoadObjectReference {
        private static final long serialVersionUID = 0;
        private static final Map references = new HashMap();

        public static synchronized JvmScopeObjectReference getInstance(Class implementationClass) {
            JvmScopeObjectReference ref = (JvmScopeObjectReference) references.get(implementationClass);
            if (ref == null) {
                ref = new JvmScopeObjectReference(implementationClass);
                references.put(implementationClass, ref);
            }
            return ref;
        }

        private JvmScopeObjectReference(Class implementationClass) {
            super(implementationClass);
        }

        public Object readResolve() {
            JvmScopeObjectReference ref = getInstance(implementationClass);
            if (implementation != null) {
                ref.set(implementation);
            }
            return ref;
        }
    }

    private static class ClassScopeObjectReference extends LazyLoadObjectReference {
        private static final long serialVersionUID = 0;
        private static final Map references = new HashMap();
        private Class originalClass;

        public static synchronized ClassScopeObjectReference getInstance(Class implementationClass, Class originalClass) {
            Map refMap = (Map) references.get(originalClass);
            if (refMap == null) {
                refMap = new HashMap();
                references.put(originalClass, refMap);
            }
            ClassScopeObjectReference ref = (ClassScopeObjectReference) refMap.get(implementationClass);
            if (ref == null) {
                ref = new ClassScopeObjectReference(implementationClass, originalClass);
                refMap.put(implementationClass, ref);
            }
            return ref;
        }

        private ClassScopeObjectReference(Class implementationClass, Class originalClass) {
            super(implementationClass);
            this.originalClass = originalClass;
        }

        public Object readResolve() {
            ClassScopeObjectReference ref = getInstance(implementationClass, originalClass);
            if (implementation != null) {
                ref.set(implementation);
            }
            return ref;
        }
    }

    private static class InstanceScopeObjectReference extends LazyLoadObjectReference {
        private static final long serialVersionUID = 0;

        public InstanceScopeObjectReference(Class implementationClass) {
            super(implementationClass);
        }
    }

    private static class ThreadScopeObjectReference extends ObjectReference {
        private static final long serialVersionUID = 0;
        private static final Map references = new HashMap();
        private final transient ThreadLocal implemetationHolder = new ThreadLocal();

        public static synchronized ThreadScopeObjectReference getInstance(Class implementationClass) {
            ThreadScopeObjectReference ref = (ThreadScopeObjectReference) references.get(implementationClass);
            if (ref == null) {
                ref = new ThreadScopeObjectReference(implementationClass);
                references.put(implementationClass, ref);
            }
            return ref;
        }

        private ThreadScopeObjectReference(Class implementationClass) {
            super(implementationClass);
        }

        public Object get() {
            Object impl = implemetationHolder.get();
            if (impl == null) {
                impl = createImplementation(implementationClass);
                set(impl);
            }
            return impl;
        }

        public void set(Object object) {
            implemetationHolder.set(object);
        }

        public Object readResolve() {
            ThreadScopeObjectReference ref = getInstance(implementationClass);
            return ref;
        }
    }
}