package net.w_horse.excelpojo.annotation;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;

import net.w_horse.excelpojo.ExcelPOJOBridge;
import net.w_horse.excelpojo.ExcelPOJOException;
import net.w_horse.excelpojo.bean.Utils;
import net.w_horse.excelpojo.converter.CellFieldConverter;
import net.w_horse.excelpojo.converter.NothingConverter;
import net.w_horse.excelpojo.excel.cellseeker.AbstractRepeatsSeeker;
import net.w_horse.excelpojo.excel.cellseeker.CellSeeker;
import net.w_horse.excelpojo.excel.cellseeker.CellSeekerManager;
import net.w_horse.excelpojo.excel.cellseeker.ConstantValueCellSeeker;
import net.w_horse.excelpojo.excel.cellseeker.HorizontalRepeatsSeeker;
import net.w_horse.excelpojo.excel.cellseeker.LabeledCellSeeker;
import net.w_horse.excelpojo.excel.cellseeker.MappedCellSeeker;
import net.w_horse.excelpojo.excel.cellseeker.PointedCellSeeker;
import net.w_horse.excelpojo.excel.cellseeker.VerticalRepeatsSeeker;
import net.w_horse.excelpojo.xml.tag.DataDirection;
import net.w_horse.excelpojo.xml.tag.RetrieveFrom;
import net.w_horse.excelpojo.xml.tag.Use;

import org.springframework.beans.BeanUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
 * アノテーションを解析してExcel検索オブジェクトの情報をセットするクラス
 *
 * @author kawahara
 *
 */
public class ExcelPOJOAnnotationParser {
	private String targetClass = "";

	/**
	 *
	 *
	 * @param targetClassProperties
	 * @param targetClass
	 * @throws LinkageError
	 * @throws ClassNotFoundException
	 * @throws ExcelPOJOException
	 */
	public void setTargetClassProperties(
			HashMap<String, CellSeeker> targetClassProperties,
			Class<?> targetClass) throws ClassNotFoundException, LinkageError, ExcelPOJOException {

		for (Field field : targetClass.getDeclaredFields()) {
			for (Annotation annotation : field.getAnnotations()) {
				// 表の場合は要素（POJO）についても未定義のフィールドをセットする
				if ((annotation instanceof VerticalRepeats)
					&& (targetClassProperties.containsKey(field.getName()))
				) {
					VerticalRepeatsSeeker seeker = (VerticalRepeatsSeeker) targetClassProperties.get(field.getName());
					setRepeatsSeekerField(seeker.getExcelPOJOBridge(),
									getRepeatsAnnnotationInfo((VerticalRepeats)annotation),
									RetrieveFrom.BOTTOM.getValue(),
									DataDirection.RIGHT.getValue());
				} else if ((annotation instanceof HorizontalRepeats)
					&& (targetClassProperties.containsKey(field.getName()))
				) {
					HorizontalRepeatsSeeker seeker = (HorizontalRepeatsSeeker) targetClassProperties.get(field.getName());
					setRepeatsSeekerField(seeker.getExcelPOJOBridge(),
									getRepeatsAnnnotationInfo((HorizontalRepeats)annotation),
									RetrieveFrom.RIGHT.getValue(),
									DataDirection.DOWN.getValue());
				}

				// もしseekerがセットされていなければ、
				// 下記の処理で新規作成されてセットされる。
				if (!targetClassProperties.containsKey(field.getName())) {
					CellSeeker seeker = buildCellSeeker(annotation);
					if (seeker == null) {
						continue;	// XPOJOのCellSeekerアノテーションでないけらば無視
					}
					seeker.set(annotation, this);
					targetClassProperties.put(field.getName(), seeker);
				}

				if (annotation instanceof Converter) {
					// Converterのセット
					CellSeeker seeker = targetClassProperties.get(field.getName());
					if (seeker == null) {
						throw new ExcelPOJOException("There is no annotation specified on the target field.");
					}
					if (!(seeker.getConverter() instanceof NothingConverter)) {
						continue;	// XMLでConverterがセットされていた場合は無視
					}
					try {
						seeker.setConverter(createCellFieldConverter((Converter)annotation));
					} catch (Throwable t) {
						throw new ExcelPOJOException(t);
					}
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private CellFieldConverter createCellFieldConverter(Converter annotation) throws Exception {
		Class<? extends CellFieldConverter> converterClass = ClassUtils.forName(annotation.converterClass());

		// コンストラクタで設定されていなかった場合
		// 引数は空の配列になり、デフォルトコンストラクタが実行される。
		String[] constractrArgs = annotation.constractorArgs().split(",");
		Object[] args = new Object[(annotation.constractorArgs().isEmpty() ? 0 : constractrArgs.length)];
		for (int i = 0; i < args.length; i++) {
			args[i] = constractrArgs[i].trim();
		}
		CellFieldConverter converter = Utils.instantiateTarget(converterClass, args);

		// プロパティで設定されていた場合
		String[] parameterArgs = annotation.properties().split(",");
		if (annotation.properties().isEmpty()) {
			return converter;	// パラメータに設定されていなければそのまま返す
		}
		for (int i = 0; i < parameterArgs.length; i++) {
			String paramValue[] = parameterArgs[i].split("=");
			String propertyName = paramValue[0].trim();
			String value = paramValue[1].trim();
			PropertyDescriptor propertyDescriptor
				= BeanUtils.getPropertyDescriptor(converterClass, propertyName);
			if (propertyDescriptor == null) {
				continue;	// 設定が実態と矛盾がある場合は無視する
			}
			ReflectionUtils.invokeMethod(propertyDescriptor.getWriteMethod(),
					converter,
					new Object[]{value});
		}
		return converter;
	}

	// CellSeekerごとに異なる内容をセットする
	public void setCellSeeker(Annotation annotation, LabeledCellSeeker seeker) {
		LabeledCell annotationLabeledCell = (LabeledCell)annotation;
		seeker.setLabel(annotationLabeledCell.label());
		seeker.setRetrieveFrom(annotationLabeledCell.retrieveFrom().getValue());
		seeker.setMargedLabel(String.valueOf(annotationLabeledCell.margedLabel()));
		seeker.setMargedRows(annotationLabeledCell.margedRows());
		seeker.setUse(annotationLabeledCell.use().getValue());
		seeker.setRange(annotationLabeledCell.range());
	}
	public void setCellSeeker(Annotation annotation, PointedCellSeeker seeker) {
		PointedCell annotationPointedCell = (PointedCell)annotation;
		seeker.setPosition(annotationPointedCell.position());
		seeker.setUse(annotationPointedCell.use().getValue());
	}
	public void setCellSeeker(Annotation annotation, ConstantValueCellSeeker seeker) {
		ConstantValue annotationConstantValue = (ConstantValue)annotation;
		seeker.setValue(annotationConstantValue.value());
	}
	public void setCellSeeker(Annotation annotation, VerticalRepeatsSeeker seeker) throws ClassNotFoundException, LinkageError, ExcelPOJOException {
		HashMap<String, String> annotationInfoMap = null;
		if (annotation instanceof Bean) {
			annotationInfoMap = getRepeatsAnnnotationInfo((Bean)annotation);
		} else {
			annotationInfoMap = getRepeatsAnnnotationInfo((VerticalRepeats)annotation);
		}
		setRepeatsSeeker(annotationInfoMap,
						seeker,
						RetrieveFrom.BOTTOM.getValue(),
						DataDirection.RIGHT.getValue());
	}
	public void setCellSeeker(Annotation annotation, HorizontalRepeatsSeeker seeker) throws ClassNotFoundException, LinkageError, ExcelPOJOException {
		HashMap<String, String> annotationInfoMap = null;
		if (annotation instanceof Bean) {
			annotationInfoMap = getRepeatsAnnnotationInfo((Bean)annotation);
		} else {
			annotationInfoMap = getRepeatsAnnnotationInfo((HorizontalRepeats)annotation);
		}
		setRepeatsSeeker(annotationInfoMap,
						seeker,
						RetrieveFrom.RIGHT.getValue(),
						DataDirection.DOWN.getValue());
	}

	public void setCellSeeker(Annotation annotation, MappedCellSeeker seeker) {
		MappedCell annotationMappedCell = (MappedCell)annotation;
		seeker.setPreviousLabel(annotationMappedCell.previousLabel());
		seeker.setPosition(annotationMappedCell.position());
		seeker.setRetrieveFrom(annotationMappedCell.retrieveFrom().getValue());
		seeker.setDataDirection(annotationMappedCell.dataDirection().getValue());
		seeker.setMargedLabel(String.valueOf(annotationMappedCell.margedLabel()));
		seeker.setUse(annotationMappedCell.use().getValue());
		seeker.setRange(annotationMappedCell.range());
	}

	private static final String ANN_ATTR_TARGET_CLASS = "targetClass";
	private static final String ANN_ATTR_LABEL = "label";
	private static final String ANN_ATTR_POSITION = "position";
	private static final String ANN_ATTR_TERMINATE = "terminate";
	private static final String ANN_ATTR_RETRIEVE_FROM = RetrieveFrom.getTagName();
	private static final String ANN_ATTR_MARGED_ROWS = "margedRows";
	private static final String ANN_ATTR_MARGED_LABEL = "margedLabel";
	private static final String ANN_ATTR_USE = Use.getTagName();
	private static final String ANN_ATTR_LIST_CLASS = "listClass";
	private static final String ANN_ATTR_SUCCEED_FIELDS = "succeedFields";
	private static final String ANN_ATTR_RANGE = "range";
	private HashMap<String, String> getRepeatsAnnnotationInfo(VerticalRepeats annotation) {
		HashMap<String, String> annotationInfoMap = new HashMap<String, String>();
		annotationInfoMap.put(ANN_ATTR_TARGET_CLASS, annotation.targetClass());
		annotationInfoMap.put(ANN_ATTR_LABEL, annotation.label());
		annotationInfoMap.put(ANN_ATTR_POSITION, annotation.position());
		annotationInfoMap.put(ANN_ATTR_TERMINATE, annotation.terminate());
		annotationInfoMap.put(ANN_ATTR_RETRIEVE_FROM, annotation.retrieveFrom().getValue());
		annotationInfoMap.put(ANN_ATTR_MARGED_ROWS, String.valueOf(annotation.margedRows()));
		annotationInfoMap.put(ANN_ATTR_MARGED_LABEL, String.valueOf(annotation.margedLabel()));
		annotationInfoMap.put(ANN_ATTR_USE, annotation.use().getValue());
		annotationInfoMap.put(ANN_ATTR_LIST_CLASS, annotation.listClass());
		annotationInfoMap.put(ANN_ATTR_SUCCEED_FIELDS, annotation.succeedFields());
		annotationInfoMap.put(ANN_ATTR_RANGE, annotation.range());
		return annotationInfoMap;
	}
	private HashMap<String, String> getRepeatsAnnnotationInfo(HorizontalRepeats annotation) {
		HashMap<String, String> annotationInfoMap = new HashMap<String, String>();
		annotationInfoMap.put(ANN_ATTR_TARGET_CLASS, annotation.targetClass());
		annotationInfoMap.put(ANN_ATTR_LABEL, annotation.label());
		annotationInfoMap.put(ANN_ATTR_POSITION, annotation.position());
		annotationInfoMap.put(ANN_ATTR_TERMINATE, annotation.terminate());
		annotationInfoMap.put(ANN_ATTR_RETRIEVE_FROM, annotation.retrieveFrom().getValue());
		annotationInfoMap.put(ANN_ATTR_MARGED_ROWS, String.valueOf(annotation.margedRows()));
		annotationInfoMap.put(ANN_ATTR_MARGED_LABEL, String.valueOf(annotation.margedLabel()));
		annotationInfoMap.put(ANN_ATTR_USE, annotation.use().getValue());
		annotationInfoMap.put(ANN_ATTR_LIST_CLASS, annotation.listClass());
		annotationInfoMap.put(ANN_ATTR_SUCCEED_FIELDS, annotation.succeedFields());
		annotationInfoMap.put(ANN_ATTR_RANGE, annotation.range());
		return annotationInfoMap;
	}
	private HashMap<String, String> getRepeatsAnnnotationInfo(Bean annotation) {
		HashMap<String, String> annotationInfoMap = new HashMap<String, String>();
		annotationInfoMap.put(ANN_ATTR_TARGET_CLASS, getTargetClass());
		annotationInfoMap.put(ANN_ATTR_LABEL, annotation.label());
		annotationInfoMap.put(ANN_ATTR_POSITION, annotation.position());
		annotationInfoMap.put(ANN_ATTR_TERMINATE, annotation.terminate());
		annotationInfoMap.put(ANN_ATTR_RETRIEVE_FROM, annotation.retrieveFrom().getValue());
		annotationInfoMap.put(ANN_ATTR_MARGED_ROWS, String.valueOf(annotation.margedRows()));
		annotationInfoMap.put(ANN_ATTR_MARGED_LABEL, String.valueOf(annotation.margedLabel()));
		annotationInfoMap.put(ANN_ATTR_USE, annotation.use().getValue());
		annotationInfoMap.put(ANN_ATTR_LIST_CLASS, annotation.listClass());
		annotationInfoMap.put(ANN_ATTR_SUCCEED_FIELDS, "");	// Beanの場合は引き継ぐ親がないので空欄に設定
		annotationInfoMap.put(ANN_ATTR_RANGE, annotation.range());
		return annotationInfoMap;
	}
	private void setRepeatsSeeker(HashMap<String, String> annotationInfoMap,
								AbstractRepeatsSeeker seeker,
								String defaultRetrieveFrom, String defaultDataDirection
							) throws ClassNotFoundException, LinkageError, ExcelPOJOException {
		seeker.setLabel(annotationInfoMap.get(ANN_ATTR_LABEL));
		seeker.setPosition(annotationInfoMap.get(ANN_ATTR_POSITION));
		seeker.setTerminate(annotationInfoMap.get(ANN_ATTR_TERMINATE));
		String retrievedFrom = annotationInfoMap.get(ANN_ATTR_RETRIEVE_FROM);
		seeker.setRetrieveFrom((retrievedFrom.isEmpty() ? defaultRetrieveFrom : retrievedFrom));
		seeker.setMargedRows(Boolean.valueOf(annotationInfoMap.get(ANN_ATTR_MARGED_ROWS)));
		seeker.setMargedLabel(annotationInfoMap.get(ANN_ATTR_MARGED_LABEL));
		seeker.setUse(annotationInfoMap.get(ANN_ATTR_USE));
		seeker.setListClass(annotationInfoMap.get(ANN_ATTR_LIST_CLASS));
		seeker.setSucceedFields(annotationInfoMap.get(ANN_ATTR_SUCCEED_FIELDS));
		seeker.setRange(annotationInfoMap.get(ANN_ATTR_RANGE));

		ExcelPOJOBridge excelPOJOBridge = new ExcelPOJOBridge();
		excelPOJOBridge.setTargetClass(annotationInfoMap.get(ANN_ATTR_TARGET_CLASS));

		// 読み込み情報の取得
		setRepeatsSeekerField(excelPOJOBridge, annotationInfoMap, defaultRetrieveFrom, defaultDataDirection);
		seeker.setExcelPOJOBridge(excelPOJOBridge);
	}

	private void setRepeatsSeekerField(ExcelPOJOBridge excelPOJOBridge,
											HashMap<String, String> annotationInfoMap,
											String defaultRetrieveFrom, String defaultDataDirection
										) throws ClassNotFoundException, LinkageError, ExcelPOJOException {
		// 読み込み情報の取得
		HashMap<String, CellSeeker> elementClassProperties = excelPOJOBridge.getTargetClassProperties();
		Class<?> elementClass = ClassUtils.forName(annotationInfoMap.get(ANN_ATTR_TARGET_CLASS));
		for (Field field : elementClass.getDeclaredFields()) {
			for (Annotation fieldAnnotation : field.getAnnotations()) {
				if (elementClassProperties.containsKey(field.getName())) {
					continue;	// XMLでセットされていれば無視
				}

				CellSeeker fieldSeeker = buildCellSeeker(fieldAnnotation);
				if (fieldSeeker == null) {
					continue;	// XPOJOのCellSeekerアノテーションでないけらば無視
				}

				fieldSeeker.set(fieldAnnotation, this);
				// 表から取得させるため、取得元情報を上書きする。
				if (fieldSeeker instanceof LabeledCellSeeker) {
					((LabeledCellSeeker)fieldSeeker).setRetrieveFrom(defaultRetrieveFrom);
				} else if (fieldSeeker instanceof MappedCellSeeker) {
					((MappedCellSeeker)fieldSeeker).setRetrieveFrom(defaultRetrieveFrom);
					((MappedCellSeeker)fieldSeeker).setDataDirection(defaultDataDirection);
				}
				elementClassProperties.put(field.getName(), fieldSeeker);
			}
		}
	}

	private CellSeeker buildCellSeeker(Annotation annotation) throws ExcelPOJOException {
		try {
			return CellSeekerManager.getCellSeekerInstance(annotation);
		} catch (Throwable e) {
			throw new ExcelPOJOException(e);
		}
	}

	public String getTargetClass() {
	    return targetClass;
	}
	public void setTargetClass(String targetClass) {
	    this.targetClass = targetClass;
	}

}
