package org.arefgard.icerya.container.parse;

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

import org.arefgard.icerya.container.ContainerFactory;
import org.arefgard.icerya.container.IceryaContainer;
import org.arefgard.icerya.container.cache.impl.NoneCacheStrategy;
import org.arefgard.icerya.container.cache.impl.SingletonCacheStrategy;
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.ConstructorArgDef;
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.ValueResourceDef;
import org.arefgard.icerya.container.injection.impl.ConstructorInjectionStrategy;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;


/**
 * Bean情報をパースするためのContentHandler。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 */
public class BeanHandler implements ContentHandler {
	
	/** containerノード(ルート要素) */
	private static final String NODE_CONTAINER = "container";
	/** meta-definitionノード */
	private static final String NODE_META_DEFINITION = "meta-definition";
	/** attributeノード */
	private static final String NODE_ATTRIBUTE = "attribute";
	/** beanノード */
	private static final String NODE_BEAN = "bean";
	/** propertyノード */
	private static final String NODE_PROPERTY = "property";
	/** constructor-argノード */
	private static final String NODE_CONSTRUCTOR_ARG = "constructor-arg";
	/** includeノード */
	private static final String NODE_INCLUDE = "include";
	/** elementノード */
	private static final String NODE_ELEMENT = "element";
	/** entryノード */
	private static final String NODE_ENTRY = "entry";
	/** aspectノード */
	private static final String NODE_ASPECT = "aspect";
	
	/** context属性 */
	private static final String ATTR_CONTEXT  = "context";
	/** id属性 */
	private static final String ATTR_ID = "id";
	/** class属性 */
	private static final String ATTR_CLASS = "class";
	/** cache属性 */
	private static final String ATTR_CACHE = "cache";
	/** init-method属性 */
	private static final String ATTR_INIT_METHOD = "init-method";
	/** destroy-method属性 */
	private static final String ATTR_DESTROY_METHOD = "destroy-method";
	/** name属性 */
	private static final String ATTR_NAME = "name";
	/** ref属性 */
	private static final String ATTR_REF = "ref";
	/** value属性 */
	private static final String ATTR_VALUE = "value";
	/** type属性 */
	private static final String ATTR_TYPE = "type";
	/** index属性 */
	private static final String ATTR_INDEX = "index";
	/** key属性 */
	private static final String ATTR_KEY = "key";
	/** pointcut属性 */
	private static final String ATTR_POINTCUT = "pointcut";
	/** advice属性 */
	private static final String ATTR_ADVICE = "advice";
	
	private String context = null;
	
	private String nowBean;
	private String nowProperty;
	private BeanResourceDef nowPropertyBean;
	private ElementDef nowElementDef;
	private EntryDef nowEntryDef;
	private int nowConstructorArg = -1;
	
	private IceryaContainer container;
	
	public BeanHandler(IceryaContainer container) {
		this.container = container;
	}
	
	public void characters(char[] ch, int start, int length)
			throws SAXException { }

	public void endDocument() throws SAXException {

	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if(qName.endsWith(NODE_BEAN)) {
			if(this.nowPropertyBean != null) {
				this.nowPropertyBean = null;
			}else {
				// <bean>
				this.nowBean = null;
				this.nowConstructorArg = -1;
			}
		}else if(qName.endsWith(NODE_PROPERTY)) {
			// <property>
			this.nowProperty = null;
		}else if(qName.endsWith(NODE_ELEMENT)) {
			// <element>
			BeanDef beanDef = container.getBeanDef(this.nowBean);
			beanDef.addElement(this.nowElementDef);
			this.nowElementDef = null;
		}else if(qName.endsWith(NODE_ENTRY)) {
			// <entry>
			BeanDef beanDef = container.getBeanDef(this.nowBean);
			beanDef.addEntry(this.nowEntryDef);
			this.nowEntryDef = null;
		}
	}

	public void endPrefixMapping(String prefix) throws SAXException {

	}

	public void ignorableWhitespace(char[] ch, int start, int length)
			throws SAXException {

	}

	public void processingInstruction(String target, String data)
			throws SAXException {

	}

	public void setDocumentLocator(Locator locator) {

	}

	public void skippedEntity(String name) throws SAXException {

	}

	public void startDocument() throws SAXException {

	}

	public void startElement(String uri, String localName, String qName,
			Attributes atts) throws SAXException {
		if(qName.endsWith(NODE_CONTAINER)) {
			// <container>
			processContainerNode(atts);
		}else if(qName.endsWith(NODE_ATTRIBUTE)) {
			// <attribute>
			processMetaDefinitionNode(atts);
		}else if(qName.endsWith(NODE_BEAN)) {
			// <bean>
			processBeanNode(atts);
		}else if(qName.endsWith(NODE_PROPERTY)) {
			// <property>
			processPropertyNode(atts);
		}else if(qName.endsWith(NODE_CONSTRUCTOR_ARG)) {
			// <constructor-arg>
			processConstructorArgNode(atts);
		}else if(qName.endsWith(NODE_INCLUDE)) {
			// <include>
			processIncludeNode(atts);
		}else if(qName.endsWith(NODE_ELEMENT)) {
			// <element>
			processElementNode(atts);
		}else if(qName.endsWith(NODE_ENTRY)) {
			// <entry>
			processEntryNode(atts);
		}else if(qName.endsWith(NODE_ASPECT)) {
			// <aspect>
			processAspectNode(atts);
		}
	}


	public void startPrefixMapping(String prefix, String uri)
			throws SAXException { }
	
	/**
	 * &lt;container&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processContainerNode(Attributes atts) throws SAXException {
		context = atts.getValue(ATTR_CONTEXT);
		if(context == null || context.equals("")) {
			throw new SAXException("context attribute is required.");
		}
		container.setContext(context);
	}

	/**
	 * &lt;meta-definition&gt;ノードを処理する。
	 * 
	 * @param atts
	 */
	private void processMetaDefinitionNode(Attributes atts) {
		container.addMetaDefinition(atts.getValue(ATTR_NAME), atts.getValue(ATTR_VALUE));
	}
	
	/**
	 * &lt;bean&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processBeanNode(Attributes atts) throws SAXException {
		if(this.nowProperty != null) {
			// プロパティに設定する無名Bean
			BeanDef beanDef = container.getBeanDef(this.nowBean);
			PropertyDef propertyDef = beanDef.getProperty(this.nowProperty);
			
			this.nowPropertyBean = new BeanResourceDef();
			this.nowPropertyBean.setContainer(container);
			
			// class属性の取得
			String clsStr = atts.getValue(ATTR_CLASS);
			if(clsStr == null || clsStr.equals("")) {
				throw new SAXException("bean tag's class attribute is required.");
			}
//			Class cls = null;
//			try {
//				cls = Class.forName(clsStr);
//			}catch(ClassNotFoundException e) {
//				throw new SAXException("Class[" + clsStr + "] is not found.");
//			}
			this.nowPropertyBean.setBeanClass(clsStr);
//			if(this.isListObject(cls)) {
//				// リスト
//				this.nowPropertyBean.setListObject(true);
//			}else if(this.isMapObject(cls)) {
//				// マップ
//				this.nowPropertyBean.setMapObject(true);
//			}
//			propertyDef.setType(cls);
			propertyDef.setResourceDef(this.nowPropertyBean);
			
		}else if(this.nowElementDef != null) {
			// List要素に設定する無名Bean
			this.nowPropertyBean = new BeanResourceDef();
			this.nowPropertyBean.setContainer(container);
			
			// class属性の取得
			String clsStr = atts.getValue(ATTR_CLASS);
			if(clsStr == null || clsStr.equals("")) {
				throw new SAXException("bean tag's class attribute is required.");
			}
//			Class cls = null;
//			try {
//				cls = Class.forName(clsStr);
//			}catch(ClassNotFoundException e) {
//				throw new SAXException("Class[" + clsStr + "] is not found.");
//			}
			this.nowPropertyBean.setBeanClass(clsStr);
//			if(this.isListObject(cls)) {
//				// リスト
//				this.nowPropertyBean.setListObject(true);
//			}else if(this.isMapObject(cls)) {
//				// マップ
//				this.nowPropertyBean.setMapObject(true);
//			}
			this.nowElementDef.setResourceDef(this.nowPropertyBean);
		}else if(this.nowEntryDef != null) {
			// Map要素に設定する無名Bean
			this.nowPropertyBean = new BeanResourceDef();
			this.nowPropertyBean.setContainer(container);
			
			// class属性の取得
			String clsStr = atts.getValue(ATTR_CLASS);
			if(clsStr == null || clsStr.equals("")) {
				throw new SAXException("bean tag's class attribute is required.");
			}
//			Class cls = null;
//			try {
//				cls = Class.forName(clsStr);
//			}catch(ClassNotFoundException e) {
//				throw new SAXException("Class[" + clsStr + "] is not found.");
//			}
			this.nowPropertyBean.setBeanClass(clsStr);
//			if(this.isListObject(cls)) {
//				// リスト
//				this.nowPropertyBean.setListObject(true);
//			}else if(this.isMapObject(cls)) {
//				// マップ
//				this.nowPropertyBean.setMapObject(true);
//			}
			this.nowEntryDef.setResourceDef(this.nowPropertyBean);			
		}else {
			BeanDef beanDef = new BeanDef();
			
			// id属性の取得
			String id = atts.getValue(ATTR_ID);
			if(id == null || id.equals("")) {
				throw new SAXException("bean tag's id attribute is required.");
			}
			if(this.container.containsBean(id)) {
				throw new SAXException("bean tag's id attribute is duplicated.");
			}
			beanDef.setBeanId(id);
			
			// class属性の取得
			String clsStr = atts.getValue(ATTR_CLASS);
			if(clsStr == null || clsStr.equals("")) {
				throw new SAXException("bean tag's class attribute is required.");
			}
//			Class cls = null;
//			try {
//				cls = Class.forName(clsStr);
//			}catch(ClassNotFoundException e) {
//				throw new SAXException("Class[" + clsStr + "] is not found.");
//			}
			beanDef.setBeanClass(clsStr);
//			if(this.isListObject(cls)) {
//				// リスト
//				beanDef.setListObject(true);
//			}else if(this.isMapObject(cls)) {
//				// マップ
//				beanDef.setMapObject(true);
//			}
			// cache属性の取得
			String cacheStr = atts.getValue(ATTR_CACHE);
			if(cacheStr == null || cacheStr.equals("") || cacheStr.equals("none")) {
				beanDef.setCacheStrategy(new NoneCacheStrategy());
			}else if(cacheStr.equals("singleton")) {
				beanDef.setCacheStrategy(new SingletonCacheStrategy());
				// singletonを指定した場合は、init-method、destroy-methodを取得する
				// init-method属性の取得
				String initMethod = atts.getValue(ATTR_INIT_METHOD);
				beanDef.setInitMethod(initMethod);
				// destroy-method属性の取得
				String destroyMethod = atts.getValue(ATTR_DESTROY_METHOD);
				beanDef.setDestroyMethod(destroyMethod);
			}else {
				throw new SAXException(cacheStr + " is not supported.");
			}
			this.nowBean = beanDef.getBeanId();
			this.container.addBeanDef(beanDef);
		}
	}
	
	/**
	 * 対象のクラス情報が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;
	}
	
	/**
	 * &lt;property&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processPropertyNode(Attributes atts) throws SAXException {
		if(this.nowProperty != null && this.nowPropertyBean != null) {
			BeanDef beanDef = this.container.getBeanDef(this.nowBean);
			PropertyDef propDef = beanDef.getProperty(this.nowProperty);
			
			BeanResourceDef resourceDef = (BeanResourceDef)propDef.getResourceDef();
			
			String name = atts.getValue(ATTR_NAME);
			if(name == null || name.equals("")) {
				throw new SAXException("property tag's name attribute is required.");
			}
			// valueの取得
			String value = atts.getValue(ATTR_VALUE);
			PropertyDef propertyDef = new PropertyDef();
			propertyDef.setName(name);
			if(value == null || value.equals("")) {
				String ref = atts.getValue(ATTR_REF);
				propertyDef.setResourceDef(new ReferenceResourceDef(ref));
			}else {
				propertyDef.setResourceDef(new ValueResourceDef(value));
			}
			resourceDef.addProperty(name, propertyDef);
		}else if(this.nowElementDef != null && this.nowPropertyBean != null) {
			BeanResourceDef resourceDef = (BeanResourceDef)this.nowElementDef.getResourceDef();
			
			String name = atts.getValue(ATTR_NAME);
			if(name == null || name.equals("")) {
				throw new SAXException("property tag's name attribute is required.");
			}
			// valueの取得
			String value = atts.getValue(ATTR_VALUE);
			PropertyDef propertyDef = new PropertyDef();
			propertyDef.setName(name);
			if(value == null || value.equals("")) {
				String ref = atts.getValue(ATTR_REF);
				propertyDef.setResourceDef(new ReferenceResourceDef(ref));
			}else {
				propertyDef.setResourceDef(new ValueResourceDef(value));
			}
			resourceDef.addProperty(name, propertyDef);
		}else if(this.nowEntryDef != null && this.nowPropertyBean != null) {
			BeanResourceDef resourceDef = (BeanResourceDef)this.nowEntryDef.getResourceDef();
			
			String name = atts.getValue(ATTR_NAME);
			if(name == null || name.equals("")) {
				throw new SAXException("property tag's name attribute is required.");
			}
			// valueの取得
			String value = atts.getValue(ATTR_VALUE);
			PropertyDef propertyDef = new PropertyDef();
			propertyDef.setName(name);
			if(value == null || value.equals("")) {
				String ref = atts.getValue(ATTR_REF);
				propertyDef.setResourceDef(new ReferenceResourceDef(ref));
			}else {
				propertyDef.setResourceDef(new ValueResourceDef(value));
			}
			resourceDef.addProperty(name, propertyDef);
		}else {
			// ***** 値を取得した段階で型を確定する必要あり
			String name = atts.getValue(ATTR_NAME);
			if(name == null || name.equals("")) {
				throw new SAXException("property tag's name attribute is required.");
			}
			// valueの取得
			String value = atts.getValue(ATTR_VALUE);
			BeanDef beanDef = container.getBeanDef(nowBean);
			PropertyDef propertyDef = new PropertyDef();
			propertyDef.setName(name);
			if(value == null || value.equals("")) {
				String ref = atts.getValue(ATTR_REF);
				propertyDef.setResourceDef(new ReferenceResourceDef(ref));
			}else {
				propertyDef.setResourceDef(new ValueResourceDef(value));
			}
			beanDef.addProperty(name, propertyDef);
			this.nowProperty = name;
		}
	}
	
	/**
	 * &lt;constructor-arg&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processConstructorArgNode(Attributes atts) throws SAXException {
		String value = atts.getValue(ATTR_VALUE);
		String ref = atts.getValue(ATTR_REF);
		String type = atts.getValue(ATTR_TYPE);
		String index = atts.getValue(ATTR_INDEX);
		
		if((value == null || value.equals(""))
				&& (ref == null || ref.equals(""))) {
			throw new SAXException("constructor-arg tag's value attribute or ref attribute are required.");
		}
		if((value != null && !value.equals(""))
				&& (ref != null && !ref.equals(""))) {
			throw new SAXException("constructor-arg tag's value attribute or ref attribute are required.");
		}
		if(type == null || value.equals("")) {
			throw new SAXException("constructor-arg tag's type attribute is required.");
		}
		ConstructorArgDef constructorArgDef = new ConstructorArgDef();
		if(value != null && !value.equals("")) {
			constructorArgDef.setResourceDef(new ValueResourceDef(value));
		}else {
			constructorArgDef.setResourceDef(new ReferenceResourceDef(ref));
		}
		try {
			constructorArgDef.setType(Class.forName(type));
		}catch(Exception e) {
			throw new SAXException("");
		}
		BeanDef beanDef = container.getBeanDef(nowBean);
		beanDef.setInjectionStrategy(new ConstructorInjectionStrategy());
		if(index == null || index.equals("")) {
			// インデックス指定なし
			beanDef.addConstructorArg(constructorArgDef);
		}else {
			// インデックス指定あり
			beanDef.setConstructorArg(Integer.parseInt(index), constructorArgDef);
		}
	}
	
	/**
	 * &lt;include&gt;ノードを処理する。
	 * <p>
	 * value属性は必須項目で、ない場合には例外をthrowする。
	 * </p>
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processIncludeNode(Attributes atts) throws SAXException {
		String ref = atts.getValue(ATTR_REF);
		if(ref == null || ref.equals("")) {
			throw new SAXException("include tag's ref attribute is required.");
		}
		try {
			IceryaContainer includeContainer = ContainerFactory.create(ref);
			this.container.addContainer(includeContainer);
		}catch(Exception e) {
			throw new SAXException("error occurred while parsing " + ref, e);
		}
	}
	
	/**
	 * &lt;element&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processElementNode(Attributes atts) throws SAXException {
		String value = atts.getValue(ATTR_VALUE);
		if(value != null && !value.equals("")) {
			this.nowElementDef = new ElementDef(new ValueResourceDef(value));
		}else {
			String ref = atts.getValue(ATTR_REF);
			if(ref != null && !ref.equals("")) {
				this.nowElementDef = new ElementDef(new ReferenceResourceDef(ref));
			}else {
				this.nowElementDef = new ElementDef();
			}
		}
	}
	
	/**
	 * &lt;entry&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processEntryNode(Attributes atts) throws SAXException {
		String key = atts.getValue(ATTR_KEY);
		this.nowEntryDef = new EntryDef();
		this.nowEntryDef.setKey(key);
		if(key == null || key.equals("")) {
			throw new SAXException("key attribute is required.");
		}
		String value = atts.getValue(ATTR_VALUE);
		BeanDef beanDef = container.getBeanDef(nowBean);
		
		if(value != null && !value.equals("")) {
			this.nowEntryDef.setResourceDef(new ValueResourceDef(value));
			beanDef.addEntry(this.nowEntryDef);
		}else {
			String ref = atts.getValue(ATTR_REF);
			if(ref != null && !ref.equals("")) {
				this.nowEntryDef.setResourceDef(new ReferenceResourceDef(ref));
				beanDef.addEntry(this.nowEntryDef);
			}
		}
	}
	
	/**
	 * &lt;aspect&gt;ノードを処理する。
	 * 
	 * @param atts
	 * @throws SAXException
	 */
	private void processAspectNode(Attributes atts) throws SAXException {
		String pointcut = atts.getValue(ATTR_POINTCUT);
		String advice = atts.getValue(ATTR_ADVICE);
		
		BeanDef beanDef = container.getBeanDef(nowBean);
		beanDef.addAspect(new AspectDef(pointcut, advice));
	}
	

}
