package org.arefgard.icerya.container.injection.impl;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.arefgard.icerya.aop.AspectProxyFactory;
import org.arefgard.icerya.container.BeanCreateException;
import org.arefgard.icerya.container.ContainerException;
import org.arefgard.icerya.container.IceryaContainer;
import org.arefgard.icerya.container.def.AspectDef;
import org.arefgard.icerya.container.def.BeanDef;
import org.arefgard.icerya.container.def.BeanResourceDef;
import org.arefgard.icerya.container.def.ElementDef;
import org.arefgard.icerya.container.def.EntryDef;
import org.arefgard.icerya.container.def.PropertyDef;
import org.arefgard.icerya.container.def.ReferenceResourceDef;
import org.arefgard.icerya.container.def.ResourceDef;
import org.arefgard.icerya.container.def.ValueResourceDef;
import org.arefgard.icerya.container.injection.InjectionStrategy;
import org.arefgard.icerya.container.util.ReflectionUtil;

/**
 * Setterを使ったインジェクション戦略。
 * 
 * @author Takashi Yamashina
 *
 */
public class SetterInjectionStrategy extends InjectionStrategy {

	/** ロガー */
	private static final Log log = LogFactory.getLog(SetterInjectionStrategy.class);
	
	@Override
	protected void generate(BeanDef beanDef) throws ContainerException {
		if(this.injectObject == null) {
			// オブジェクトが存在しない場合
			if(beanDef == null) {
				log.error("Illegal BeanDef.");
				throw new ContainerException("Illegal Argument.");
			}
			String id = beanDef.getBeanId();
			Class cls = null;
			
			try {
				cls = Class.forName(beanDef.getBeanClass());
			}catch(Exception e) {
				throw new ContainerException("class[" + beanDef.getBeanClass() + "] is not found");
			}
			
			// AOPの実行
			List<AspectDef> aspects = beanDef.getAspects();
			if(aspects != null && aspects.size() != 0) {
				AspectProxyFactory factory = new AspectProxyFactory(cls);
				for(AspectDef aspectDef : aspects) {
					List<String> patterns = new java.util.ArrayList<String>();
					patterns.add(aspectDef.getPointcut());
					factory.addInterceptor(patterns, beanDef.getContainer().getBean(aspectDef.getAdvice()));
				}
				
				cls = factory.getProxy();
			}
			
			// オブジェクトの種類を解決する
			if(this.isListObject(cls)) {
				// リスト
				beanDef.setListObject(true);
			}else if(this.isMapObject(cls)) {
				// マップ
				beanDef.setMapObject(true);
			}
			
			if(log.isDebugEnabled()) {
				log.debug("  id = " + id + ", className = " + cls.getName());
			}
			
			String context = beanDef.getContext();
			try {
				
				this.injectObject = ReflectionUtil.generate(cls);
				if(context != null) {
					this.injectObject = ReflectionUtil.convertObject(this.injectObject, context);
				}
			} catch (Exception e) {
				log.error("Bean creation failed.", e);
				throw new BeanCreateException("Bean creation failed.", e);
			}
		}
	}
	
	@Override
	protected void injection(BeanDef beanDef) throws ContainerException {
		
		IceryaContainer container = beanDef.getContainer();
		if(beanDef.isListObject()) {
			// Listオブジェクトの場合
			List<ElementDef> elements = beanDef.getElements();
			List list = (List)this.injectObject;
			try {
				for(ElementDef element : elements) {
					Object value = null;
					ResourceDef resourceDef = element.getResourceDef();
					if(resourceDef instanceof ValueResourceDef) {
						ValueResourceDef valueDef = (ValueResourceDef)resourceDef;
						value = valueDef.getValue();
					}else if(resourceDef instanceof ReferenceResourceDef) {
						ReferenceResourceDef refDef = (ReferenceResourceDef)resourceDef;
						value = container.getBean(refDef.getRef());
					}else if(resourceDef instanceof BeanResourceDef) {
						BeanResourceDef beanResourceDef = (BeanResourceDef)resourceDef;
						InjectionStrategy injection = beanResourceDef.getInjectionStrategy();
						injection.inject(beanResourceDef);
						value = injection.getInjectObject();
					}
					list.add(value);
				}
			}catch(Exception e) {
				log.error("Bean creation failed.", e);
				throw new BeanCreateException("Bean creation failed.", e);
			}
			this.injectObject = list;
		}else if(beanDef.isMapObject()) {
			// Mapオブジェクトの場合
			List<EntryDef> entries = beanDef.getEntries();
			Map map = (Map)this.injectObject;
			try {
				for(EntryDef entry : entries) {
					String key = entry.getKey();
					Object value = null;
					ResourceDef resourceDef = entry.getResourceDef();
					if(resourceDef instanceof ValueResourceDef) {
						ValueResourceDef valueDef = (ValueResourceDef)resourceDef;
						value = valueDef.getValue();
					}else if(resourceDef instanceof ReferenceResourceDef) {
						ReferenceResourceDef refDef = (ReferenceResourceDef)resourceDef;
						value = container.getBean(refDef.getRef());
					}else if(resourceDef instanceof BeanResourceDef) {
						BeanResourceDef beanResourceDef = (BeanResourceDef)resourceDef;
						InjectionStrategy injection = beanResourceDef.getInjectionStrategy();
						injection.inject(beanResourceDef);
						value = injection.getInjectObject();
					}
					map.put(key, value);
				}
			}catch(Exception e) {
				log.error("Bean creation failed.", e);
				throw new BeanCreateException("Bean creation failed.", e);
			}
			this.injectObject = map;
		}else {
			// 通常のオブジェクト
			Map<String, PropertyDef> prop = beanDef.getProperties();
			try {
				
				// プロパティの処理
				if (prop.size() > 0) {
					// propertyあり
					for (Iterator<String> itr = prop.keySet().iterator(); itr.hasNext();) {
						String key = itr.next();
						Object value = null;
						
						PropertyDef propertyDef = prop.get(key);
						
						ResourceDef resourceDef = propertyDef.getResourceDef();
						
						if(resourceDef instanceof ValueResourceDef) {
							ValueResourceDef valueDef = (ValueResourceDef)resourceDef;
							value = valueDef.getValue();
						}else if(resourceDef instanceof ReferenceResourceDef) {
							ReferenceResourceDef refDef = (ReferenceResourceDef)resourceDef;
							value = container.getBean(refDef.getRef());
						}else if(resourceDef instanceof BeanResourceDef) {
							BeanResourceDef beanResourceDef = (BeanResourceDef)resourceDef;
							InjectionStrategy injection = beanResourceDef.getInjectionStrategy();
							injection.inject(beanResourceDef);
							value = injection.getInjectObject();
						}
						ReflectionUtil.setProperty(this.injectObject, key, value);
					}
				}
			} catch (Exception e) {
				log.error("Bean creation failed.", e);
				throw new BeanCreateException("Bean creation failed.", e);
			}
			
			// init-methodを実行
			try {
				String initMethod = beanDef.getInitMethod();
				if(initMethod != null && !initMethod.equals("")) {
					// destroyMethodを実行
					ReflectionUtil.execute(this.injectObject, initMethod, null);
				}
			}catch(Exception e) {
				e.printStackTrace();
				throw new ContainerException("", e);
			}
		}
	}
	
	/**
	 * 対象のクラス情報がListインタフェースを実装しているかどうかを
	 * 調べる。
	 * 
	 * @param cls
	 * @return
	 */
	private boolean isListObject(Class cls) {
		Class[] ifcs = cls.getInterfaces();
		
		for(Class ifc : ifcs) {
			if(ifc.equals(List.class)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 対象のクラス情報がMapインタフェースを実装しているかどうかを
	 * 調べる。
	 * 
	 * @param cls
	 * @return
	 */
	private boolean isMapObject(Class cls) {
		Class[] ifcs = cls.getInterfaces();
		
		for(Class ifc : ifcs) {
			if(ifc.equals(Map.class)) {
				return true;
			}
		}
		return false;
	}

}
