package org.arefgard.icerya.container;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.arefgard.icerya.container.cache.CacheStrategy;
import org.arefgard.icerya.container.def.BeanDef;
import org.arefgard.icerya.container.injection.InjectionStrategy;
import org.arefgard.icerya.container.injection.impl.ParameterizedInjectionStrategy;
import org.arefgard.icerya.container.util.ReflectionUtil;

/**
 * DIコンテナ。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 */
public class IceryaContainer {

	/** ロガー */
	private static final Log log = LogFactory.getLog(IceryaContainer.class);
	
	/** コンテキスト名 */
	private String context = null;
	
	/** 内部コンテナ(BeanIdをキーにBean定義を保持) */
	private Map<String, BeanDef> internal = new java.util.HashMap<String, BeanDef>();
	
	/** インクルードしたコンテナ */
	private List<IceryaContainer> includeContainer = new java.util.ArrayList<IceryaContainer>();
	
	/** コンテナメタ定義 */
	private Map<String, String> meta = new java.util.HashMap<String, String>();
	
	/**
	 * デフォルトコンストラクタ。<br/>
	 * <br/>
	 * 同一パッケージからのみ使われるようにする。
	 */
	IceryaContainer() {
		
	}
	
	public void addBeanDef(String beanId, String cls) {
		BeanDef beanDef = new BeanDef();
		beanDef.setBeanId(beanId);
		beanDef.setContainer(this);
		this.internal.put(beanId, beanDef);
	}
	
	/**
	 * BeanEntityオブジェクトを内部コンテナに追加する。
	 * @param entity BeanEntityオブジェクト
	 */
	public void addBeanDef(BeanDef entity) {
		entity.setContainer(this);
		this.internal.put(entity.getBeanId(), entity);
	}
	
	/**
	 * BeanEntityオブジェクトを内部コンテナから取り出す。
	 * @param id bean id
	 * @return BeanEntityオブジェクト
	 */
	public BeanDef getBeanDef(String id) {
		return this.internal.get(id);
	}
	
	public Map<String, BeanDef> getAllBeanDef() {
		return this.internal;
	}
	
	/**
	 * Beanを取得する。
	 * 
	 * @param id Bean ID
	 * @return 生成したオブジェクト
	 * @throws ContainerException Beanの生成に失敗
	 */
	public Object getBean(String id) throws ContainerException {
		if(log.isDebugEnabled()) {
			log.debug("---[get bean]---");
		}
		if(id == null || id.equals("")) {
			log.error("Illegal Argument[id=" + id + "].");
			throw new ContainerException("Illegal Argument[id=" + id + "].");
		}
		BeanDef beanDef = this.internal.get(id);
		if(beanDef == null) {
			// インクルードされたコンテナから探す
			int length = this.includeContainer.size();
			
			for(int i = 0; i < length; i++) {
				IceryaContainer container = this.includeContainer.get(i);
				beanDef = container.getBeanDef(id);
				if(beanDef != null) {
					break;
				}
			}
		}
		
		if(beanDef == null) {
			log.error("ID[" + id + "] is not exitst.");
			throw new ContainerException("ID[" + id + "] is not exist.");
		}
		
		Object result = null;
		// キャッシュ戦略(キャッシュの取得)実行
		CacheStrategy cache = beanDef.getCacheStrategy();
		result = cache.getCache();
		
		// 生成戦略実行
		if(result == null) {
			// インジェクション戦略実行
			InjectionStrategy injection = beanDef.getInjectionStrategy();
			if(injection != null) {
				injection.inject(beanDef);
				result = injection.getInjectObject();
			}
			
			// 再度、キャッシュ戦略(キャッシュの保存)実行
			cache.save(result);

			return result;
		}else {
			return result;
		}
	}
	
	public <T> T getBean(String id, Class<T> cls) throws ContainerException {
		return (T)getBean(id);
	}
	
	/**
	 * パラメタライズドインジェクション用インタフェース。
	 * 
	 * @param id
	 * @param args
	 * @return
	 */
	public Object getBean(String id, Object ... args) throws ContainerException {
		if(log.isDebugEnabled()) {
			log.debug("---[get bean]---");
		}
		if(id == null || id.equals("")) {
			log.error("Illegal Argument[id=" + id + "].");
			throw new ContainerException("Illegal Argument[id=" + id + "].");
		}
		BeanDef beanDef = this.internal.get(id);
		if(beanDef == null) {
			// インクルードされたコンテナから探す
			int length = this.includeContainer.size();
			
			for(int i = 0; i < length; i++) {
				IceryaContainer container = this.includeContainer.get(i);
				beanDef = container.getBeanDef(id);
				if(beanDef != null) {
					break;
				}
			}
		}
		
		if(beanDef == null) {
			log.error("ID[" + id + "] is not exitst.");
			throw new ContainerException("ID[" + id + "] is not exist.");
		}
		
		Object result = null;
		// キャッシュ戦略(キャッシュの取得)実行
		CacheStrategy cache = beanDef.getCacheStrategy();
		result = cache.getCache();
		
		if(result == null) {
			// インジェクション戦略実行
			InjectionStrategy injection = beanDef.getInjectionStrategy();
			
			// ParameterizedInjectionStrategyの場合は引数を設定する
			if(injection instanceof ParameterizedInjectionStrategy) {
				((ParameterizedInjectionStrategy)injection).add(args);
			}
			if(injection != null) {
				injection.inject(beanDef);
				result = injection.getInjectObject();
			}

			// 再度、キャッシュ戦略(キャッシュの保存)実行
			cache.save(result);

			return result;
		}else {
			return result;
		}
	}
	
	public <T> T getBean(String id, Class<T>cls, Object ... args) throws ContainerException {
		return (T)getBean(id, args);
	}
	
	public void injectDependency(String id, Object obj) throws ContainerException {
		if(!this.internal.containsKey(id)) {
			throw new ContainerException("");
		}else {
			BeanDef beanDef = this.internal.get(id);
			InjectionStrategy injection = beanDef.getInjectionStrategy();
			injection.setInjectObject(obj);
			injection.inject(beanDef);
		}
	}
	
	public boolean containsBean(Object key) {
		return this.internal.containsKey(key);
	}
	
	/**
	 * コンテナの破棄時に実行されるメソッド。
	 * <br/>
	 * <br/>
	 * DIコンテナで管理されているすべてのdestroyMethodを実行する。
	 * 
	 * @throws Throwable
	 */
	protected void finalize() throws Throwable {
		
		Set<String> keys = this.internal.keySet();
		
		for(Iterator<String> itr = keys.iterator(); itr.hasNext();) {
			String key = itr.next();
			BeanDef beanDef = this.internal.get(key);
			String destroyMethod = beanDef.getDestroyMethod();
			Object obj = beanDef.getCacheStrategy().getCache();
			if(destroyMethod != null && !destroyMethod.equals("")) {
				// destroyMethodを実行
				ReflectionUtil.execute(obj, destroyMethod, null);
			}
		}
		super.finalize();
	}
	
	public void addContainer(IceryaContainer includeContainer) {
		this.includeContainer.add(includeContainer);
	}
	
	public String getMetaDefinition(String key) {
		return this.meta.get(key);
	}
	
	public void addMetaDefinition(String key, String value) {
		this.meta.put(key, value);
	}

	public String getContext() {
		return context;
	}

	public void setContext(String context) {
		this.context = context;
	}
	
	public void weaving() {
		
	}
}
