/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.gatein.cdi.contexts;

import java.util.HashMap;
import java.util.Map;

import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.PassivationCapable;

import org.exoplatform.portal.pc.aspects.PortletLifecyclePhaseInterceptor;
import org.gatein.cdi.contexts.beanstore.BeanStore;
import org.gatein.cdi.contexts.beanstore.BeanStoreInstance;
import org.gatein.cdi.contexts.beanstore.LockedBean;

/**
 * @author <a href="mailto:nscavell@redhat.com">Nick Scavelli</a>
 * @author <a href="http://community.jboss.org/people/kenfinni">Ken Finnigan</a>
 */
public abstract class AbstractCDIPortletContext implements Context {

    private final ThreadLocal<BeanStore> beanStore;
    private final ThreadLocal<Map<String, PortletRequestLifecycle>> lifecycles;
    private final ThreadLocal<PortletRequestLifecycle> currentLifecycle;
    private final boolean multithreaded;

    protected AbstractCDIPortletContext(boolean multithreaded) {
        this.multithreaded = multithreaded;
        this.beanStore = new ThreadLocal<BeanStore>();
        this.lifecycles = new ThreadLocal<Map<String, PortletRequestLifecycle>>();
        this.currentLifecycle = new ThreadLocal<PortletRequestLifecycle>();
    }

    @Override
    public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
        if (!isActive()) {
            throw new ContextNotActiveException();
        }

        BeanStore beanStore = getBeanStore();
        if (beanStore == null) {
            return null;
        }

        String id = getId(contextual);
        BeanStoreInstance<T> beanInstance = beanStore.getBean(id);
        if (beanInstance != null) {
            return beanInstance.getInstance();
        } else if (creationalContext != null) {
            LockedBean lock = null;
            try {
                if (multithreaded) {
                    lock = beanStore.lock(id);
                    beanInstance = beanStore.getBean(id);
                    if (beanInstance != null) {
                        return beanInstance.getInstance();
                    }
                }

                T instance = contextual.create(creationalContext);
                if (instance != null) {
                    beanInstance = new SerializableBeanStoreInstance<T>(contextual, instance, creationalContext);
                    beanStore.put(id, beanInstance);
                }
                return instance;
            } finally {
                if (lock != null) {
                    lock.unlock();
                }
            }
        } else {
            return null;
        }
    }

    @Override
    public <T> T get(Contextual<T> contextual) {
        return get(contextual, null);
    }

    @Override
    public boolean isActive() {
        return currentLifecycle.get() != null;
    }

    protected void setCurrentLifecycle(String windowId, PortletRequestLifecycle lifecycle) {
        if (lifecycle == null) {
            currentLifecycle.remove();
            Map<String, PortletRequestLifecycle> map = lifecycles.get();
            if (null != map) {
                map.remove(windowId);
            }
        } else {
            currentLifecycle.set(lifecycle);
            Map<String, PortletRequestLifecycle> map = lifecycles.get();
            if (map == null) {
                map = new HashMap<String, PortletRequestLifecycle>();
                lifecycles.set(map);
            }
            map.put(windowId, lifecycle);
        }
    }

    protected PortletRequestLifecycle getLifecycle(String windowId) {
        Map<String, PortletRequestLifecycle> map = lifecycles.get();
        if (map == null) {
            map = new HashMap<String, PortletRequestLifecycle>();
            lifecycles.set(map);
        }

        return map.get(windowId);
    }

    protected BeanStore getBeanStore() {
        return beanStore.get();
    }

    protected void setBeanStore(BeanStore beanStore) {
        if (beanStore == null) {
            this.beanStore.remove();
        } else {
            this.beanStore.set(beanStore);
        }
    }

    protected void destroy(String windowId) {
        try {
            BeanStore store = getBeanStore();
            if (store != null) {
                store.destroy(windowId);
            }
        } finally {
            Map<String, PortletRequestLifecycle> map = lifecycles.get();
            if (map != null) {
                map.remove(windowId);
                if (map.isEmpty()) {
                    lifecycles.remove();
                }
            }
        }
    }

    protected void destroy() {
        try {
            BeanStore store = getBeanStore();
            if (store != null) {
                store.destroy();
            }
        } finally {
            cleanup();
        }
    }

    protected void cleanup() {
        lifecycles.remove();
        beanStore.remove();
    }

    //TODO: Need to revisit this implementation
    private static String getId(Contextual contextual) {
        String id;
        if (contextual instanceof PassivationCapable) {
            id = ((PassivationCapable) contextual).getId();
        } else {
            id = "" + contextual.hashCode();
        }

        return windowId() + "#" + id;
    }

    private static String windowId() {
        return PortletLifecyclePhaseInterceptor.currentWindowId();
    }
}
