/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.wicket.cdi;

import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.spi.AnnotatedType;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.InjectionTarget;

import org.apache.wicket.util.collections.ClassMetaCache;

/**
 * Manages lifecycle of non-contextual (non-CDI-managed) objects
 * 
 * @param <T>
 * @author igor
 */
public class NonContextual<T>
{
	private static final Object lock = new Object();
	private static volatile Map<BeanManager, ClassMetaCache<NonContextual<?>>> cache = Collections
			.emptyMap();

	final InjectionTarget<T> it;

	/**
	 * Undeploys the looked up bean manager from cache
	 */
	public static void undeploy()
	{
		if (cache.containsKey(BeanManagerLookup.lookup()))
		{
			synchronized (lock)
			{
				// copy-on-write the cache
				Map<BeanManager, ClassMetaCache<NonContextual<?>>> newCache = new WeakHashMap<BeanManager, ClassMetaCache<NonContextual<?>>>(
						cache);
				newCache.remove(BeanManagerLookup.lookup());
				cache = Collections.unmodifiableMap(newCache);
			}
		}
	}

	/**
	 * Convenience factory method for an instance, see {@link #of(Class)}.
	 * 
	 * @param <T>
	 * @param t
	 * @return The NonContextual for the instance's class
	 */
	@SuppressWarnings("unchecked")
	public static <T> NonContextual<T> of(T t) {
		// cast is necessary for Eclipse compiler :/
		return (NonContextual<T>)of(t.getClass());
	}

	/**
	 * Factory method for creating non-contextual instances
	 * 
	 * @param <T>
	 * @param clazz
	 * @return The NonContextual for the given class
	 */
	public static <T> NonContextual<T> of(Class<? extends T> clazz)
	{
		ClassMetaCache<NonContextual<?>> meta = getCache();

		@SuppressWarnings("unchecked")
		NonContextual<T> nc = (NonContextual<T>)meta.get(clazz);

		if (nc == null)
		{
			nc = new NonContextual<T>(clazz);
			meta.put(clazz, nc);
		}
		return nc;
	}

	private static ClassMetaCache<NonContextual<?>> getCache()
	{
		ClassMetaCache<NonContextual<?>> meta = cache.get(BeanManagerLookup.lookup());
		if (meta == null)
		{
			synchronized (lock)
			{
				BeanManager manager = BeanManagerLookup.lookup();
				meta = cache.get(manager);
				if (meta == null)
				{
					meta = new ClassMetaCache<NonContextual<?>>();

					// copy-on-write the cache
					Map<BeanManager, ClassMetaCache<NonContextual<?>>> newCache = new WeakHashMap<BeanManager, ClassMetaCache<NonContextual<?>>>(
							cache);
					newCache.put(manager, meta);
					cache = Collections.unmodifiableMap(newCache);
				}
			}
		}
		return meta;
	}

	@SuppressWarnings("unchecked")
	private NonContextual(Class<? extends T> clazz)
	{
		BeanManager manager = BeanManagerLookup.lookup();
		AnnotatedType<? extends T> type = manager.createAnnotatedType(clazz);
		this.it = (InjectionTarget<T>) manager.getInjectionTargetFactory(type)
			.createInjectionTarget(null);
	}

	/**
	 * Injects the instance and calls any {@link PostConstruct} methods
	 * 
	 * @param instance
	 */
	public void postConstruct(T instance)
	{
		CreationalContext<T> cc = BeanManagerLookup.lookup().createCreationalContext(null);
		it.inject(instance, cc);
		it.postConstruct(instance);
	}

	/**
	 * Injects the instance
	 * 
	 * @param instance
	 */
	public void inject(T instance)
	{
		CreationalContext<T> cc = BeanManagerLookup.lookup().createCreationalContext(null);
		it.inject(instance, cc);
	}

	/**
	 * Calls any {@link PreDestroy} methods and destroys any injected
	 * dependencies that need to be destroyed.
	 * 
	 * @param instance
	 */
	public void preDestroy(T instance)
	{
		it.preDestroy(instance);
	}
}
