package net.w_horse.excelpojo.xml;

import java.util.HashMap;
import java.util.List;


import net.w_horse.excelpojo.ExcelPOJOBridge;
import net.w_horse.excelpojo.excel.AbstractCellSeeker;
import net.w_horse.excelpojo.excel.AbstractRepeatsSeeker;
import net.w_horse.excelpojo.excel.ConstantValueCellSeeker;
import net.w_horse.excelpojo.excel.HorizontalRepeatsSeeker;
import net.w_horse.excelpojo.excel.LabeledCellSeeker;
import net.w_horse.excelpojo.excel.MappedCellSeeker;
import net.w_horse.excelpojo.excel.PointedCellSeeker;
import net.w_horse.excelpojo.excel.VerticalRepeatsSeeker;
import net.w_horse.excelpojo.xml.tag.DataDirection;
import net.w_horse.excelpojo.xml.tag.RetrieveFrom;
import net.w_horse.excelpojo.xml.tag.RetrieveType;
import net.w_horse.excelpojo.xml.tag.Use;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

public class ExcelPOJOXmlParser extends AbstractSingleBeanDefinitionParser {

	private static final String TAG_NM_SHEET_NAME = "sheetName";
	private static final String TAG_NM_TARGET_CLASS = "targetClass";
	private static final String TAG_NM_TARGET_BEAN_PROPERTY = "targetBean-property";
	private static final String TAG_NM_RETRIEVE_TYPE = RetrieveType.getTagName();
	private static final String TAG_NM_LABEL = "label";
	private static final String TAG_NM_PREVIOUS_LABEL = "previousLabel";
	private static final String TAG_NM_POSITION = "position";
	private static final String TAG_NM_RETRIEVE_FROM = RetrieveFrom.getTagName();
	private static final String TAG_NM_VALUE = "value";
	private static final String TAG_NM_MARGED_LABEL = "margedLabel";
	private static final String TAG_NM_MARGED_ROWS = "margedRows";
	private static final String TAG_NM_TERMINATE = "terminate";
	private static final String TAG_NM_USE = Use.getTagName();
	private static final String TAG_NM_DATA_DIRECTION = DataDirection.getTagName();
	private static final String TAG_NM_NAME = "name";
	private static final String TAG_NM_BEAN = "bean";
	private static final String TAG_NM_REF = "ref";
	private static final String TAG_NM_ELEMENT_BEAN = "elementBean";
	private static final String TAG_NM_LIST_CLASS = "list-class";

	protected Class<?> getBeanClass(Element element) {
		return ExcelPOJOBridge.class;
	}

	@SuppressWarnings("unchecked")
	protected void doParse(Element element, BeanDefinitionBuilder bean) {
		// シート名の取得
		String sheetName;
		if (element.hasAttribute(TAG_NM_SHEET_NAME)) {
			sheetName = element.getAttribute(TAG_NM_SHEET_NAME);
		} else {
			sheetName = DomUtils.getChildElementValueByTagName(element, TAG_NM_SHEET_NAME);
		}
		bean.addPropertyValue(TAG_NM_SHEET_NAME, sheetName);

		// ターゲットの取得
		String targetClass = element.getAttribute(TAG_NM_TARGET_CLASS);
		bean.addPropertyValue(TAG_NM_TARGET_CLASS, targetClass);

		if (targetClass.isEmpty()) {
			AbstractCellSeeker seeker = buildCellSeeker(element.getAttribute(TAG_NM_RETRIEVE_TYPE));
			Element beanElement = DomUtils.getChildElementByTagName(element, TAG_NM_ELEMENT_BEAN);
			if (beanElement == null) {
				beanElement = element;
			}
			setElementBeanAttribute(beanElement, element);
			seeker.set(beanElement, this);
			bean.addPropertyValue("cellSeeker", seeker);
		}

		// 読み込み情報の取得
		HashMap<String, AbstractCellSeeker> targetClassProperties = new HashMap<String, AbstractCellSeeker>();
		List<Element> nodeList = DomUtils.getChildElementsByTagName(element, TAG_NM_TARGET_BEAN_PROPERTY);
		for(Element child : nodeList) {
			AbstractCellSeeker seeker = buildCellSeeker(child.getAttribute(TAG_NM_RETRIEVE_TYPE));
			seeker.set(child, this);
			targetClassProperties.put(child.getAttribute(TAG_NM_NAME), seeker);
		}
		bean.addPropertyValue("targetClassProperties", targetClassProperties);
	}

	// CellSeekerごとに異なる内容をセットする
	public void setCellSeeker(Element element, LabeledCellSeeker seeker) {
		seeker.setLabel(element.getAttribute(TAG_NM_LABEL));
		seeker.setRetrieveFrom(element.getAttribute(TAG_NM_RETRIEVE_FROM));
		seeker.setMargedLabel(element.getAttribute(TAG_NM_MARGED_LABEL));
		seeker.setMargedRows(Boolean.valueOf(element.getAttribute(TAG_NM_MARGED_ROWS)));
		seeker.setUse(element.getAttribute(TAG_NM_USE));
	}
	public void setCellSeeker(Element element, PointedCellSeeker seeker) {
		seeker.setPosition(element.getAttribute(TAG_NM_POSITION));
		seeker.setUse(element.getAttribute(TAG_NM_USE));
	}
	public void setCellSeeker(Element element, ConstantValueCellSeeker seeker) {
		String value;
		if (element.hasAttribute(TAG_NM_VALUE)) {
			value = element.getAttribute(TAG_NM_VALUE);
		} else {
			value = DomUtils.getChildElementValueByTagName(element, TAG_NM_VALUE);
		}
		seeker.setValue(value);
	}
	public void setCellSeeker(Element element, VerticalRepeatsSeeker seeker) {
		setRepeatsSeeker(element, seeker,
					RetrieveFrom.BOTTOM.getValue(),
					DataDirection.RIGHT.getValue());
	}
	public void setCellSeeker(Element element, HorizontalRepeatsSeeker seeker) {
		setRepeatsSeeker(element, seeker,
					RetrieveFrom.RIGHT.getValue(),
					DataDirection.DOWN.getValue());
	}
	public void setCellSeeker(Element element, MappedCellSeeker seeker) {
		seeker.setPreviousLabel(element.getAttribute(TAG_NM_PREVIOUS_LABEL));
		seeker.setPosition(element.getAttribute(TAG_NM_POSITION));
		seeker.setRetrieveFrom(element.getAttribute(TAG_NM_RETRIEVE_FROM));
		seeker.setDataDirection(element.getAttribute(TAG_NM_DATA_DIRECTION));
		seeker.setMargedLabel(element.getAttribute(TAG_NM_MARGED_LABEL));
		seeker.setUse(element.getAttribute(TAG_NM_USE));
	}

	private void setElementBeanAttribute(Element elementBean, Element elementParent) {
		elementBean.setAttribute(TAG_NM_LABEL, elementParent.getAttribute(TAG_NM_LABEL));
		elementBean.setAttribute(TAG_NM_POSITION, elementParent.getAttribute(TAG_NM_POSITION));
		elementBean.setAttribute(TAG_NM_RETRIEVE_FROM, elementParent.getAttribute(TAG_NM_RETRIEVE_FROM));
		elementBean.setAttribute(TAG_NM_TERMINATE, elementParent.getAttribute(TAG_NM_TERMINATE));
		elementBean.setAttribute(TAG_NM_PREVIOUS_LABEL, elementParent.getAttribute(TAG_NM_PREVIOUS_LABEL));
		elementBean.setAttribute(TAG_NM_MARGED_ROWS, elementParent.getAttribute(TAG_NM_MARGED_ROWS));
		elementBean.setAttribute(TAG_NM_LIST_CLASS, elementParent.getAttribute(TAG_NM_LIST_CLASS));
		elementBean.setAttribute(TAG_NM_USE, elementParent.getAttribute(TAG_NM_USE));
	}

	@SuppressWarnings("unchecked")
	private void setRepeatsSeeker(Element element, AbstractRepeatsSeeker seeker,
									String defaultRetrieveFrom, String dataDirection) {
		seeker.setLabel(element.getAttribute(TAG_NM_LABEL));
		seeker.setPosition(element.getAttribute(TAG_NM_POSITION));
		seeker.setTerminate(element.getAttribute(TAG_NM_TERMINATE));
		String retrievedFrom = element.getAttribute(TAG_NM_RETRIEVE_FROM);
		seeker.setRetrieveFrom((retrievedFrom.isEmpty() ? defaultRetrieveFrom : retrievedFrom));
		seeker.setMargedRows(Boolean.valueOf(element.getAttribute(TAG_NM_MARGED_ROWS)));
		seeker.setUse(element.getAttribute(TAG_NM_USE));
		seeker.setListClass(element.getAttribute(TAG_NM_LIST_CLASS));

		Element beanElement = getChildBeanElement(element);
		ExcelPOJOBridge excelPOJOBridge = new ExcelPOJOBridge();
		excelPOJOBridge.setTargetClass(beanElement.getAttribute(TAG_NM_TARGET_CLASS));

		// 読み込み情報の取得
		HashMap<String, AbstractCellSeeker> elementClassProperties = new HashMap<String, AbstractCellSeeker>();

		List<Element> fieldList = DomUtils.getChildElementsByTagName(beanElement, TAG_NM_TARGET_BEAN_PROPERTY);
		for(Element field : fieldList) {
			AbstractCellSeeker fieldSseeker = buildCellSeeker(field.getAttribute(TAG_NM_RETRIEVE_TYPE));
			field.setAttribute(RetrieveFrom.getTagName(), defaultRetrieveFrom);
			field.setAttribute(TAG_NM_DATA_DIRECTION, dataDirection);
			fieldSseeker.set(field, this);
			elementClassProperties.put(field.getAttribute(TAG_NM_NAME), fieldSseeker);
		}
		excelPOJOBridge.setTargetClassProperties(elementClassProperties);
		seeker.setExcelPOJOBridge(excelPOJOBridge);
	}

	private Element getChildBeanElement(Element element) {
		Element child = DomUtils.getChildElementByTagName(element, TAG_NM_BEAN);
		if (child == null) {
			// 直接タグに要素が書き込まれていない場合は
			// ref から参照先を取得する
			Element ref = DomUtils.getChildElementByTagName(element, TAG_NM_REF);
			child = ref.getOwnerDocument().getElementById(ref.getAttribute(TAG_NM_BEAN));
		}
		return child;
	}
	private AbstractCellSeeker buildCellSeeker(String retrieveType) {
		AbstractCellSeeker seeker = null;
		if (RetrieveType.LABELED_CELL.equals(retrieveType)) {
			seeker = new LabeledCellSeeker();
		} else if (RetrieveType.POINTED_CELL.equals(retrieveType)) {
			seeker = new PointedCellSeeker();
		} else if (RetrieveType.CONSTANT_VALUE.equals(retrieveType)) {
			seeker = new ConstantValueCellSeeker();
		} else if (RetrieveType.VERTICAL_REPEATS.equals(retrieveType)) {
			seeker = new VerticalRepeatsSeeker();
		} else if (RetrieveType.HORIZONTAL_REPEATS.equals(retrieveType)) {
			seeker = new HorizontalRepeatsSeeker();
		} else if (RetrieveType.MAPPED_CELL.equals(retrieveType)) {
			seeker = new MappedCellSeeker();
		}
		return seeker;
	}

}
