package net.java.amateras.xlsbeans.processor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import jxl.Cell;
import jxl.Sheet;
import jxl.format.Border;
import jxl.format.BorderLineStyle;
import jxl.format.CellFormat;
import net.java.amateras.xlsbeans.NeedPostProcess;
import net.java.amateras.xlsbeans.Utils;
import net.java.amateras.xlsbeans.XLSBeansException;
import net.java.amateras.xlsbeans.annotation.Column;
import net.java.amateras.xlsbeans.annotation.HorizontalRecords;
import net.java.amateras.xlsbeans.annotation.MapColumns;
import net.java.amateras.xlsbeans.annotation.PostProcess;
import net.java.amateras.xlsbeans.annotation.RecordTerminal;
import net.java.amateras.xlsbeans.xml.AnnotationReader;

/**
 * The {@link net.java.amateras.xlsbeans.processor.FieldProcessor} 
 * inplementation for {@link net.java.amateras.xlsbeans.annotation.HorizontalRecords}.
 * 
 * @author Naoki Takezoe
 * @see net.java.amateras.xlsbeans.annotation.HorizontalRecords
 */
public class HorizontalRecordsProcessor implements FieldProcessor {

	public void doProcess(Sheet sheet, Object obj, Method setter,
			Annotation ann, AnnotationReader reader,
			List<NeedPostProcess> needPostProcess) throws Exception {
		
		HorizontalRecords records = (HorizontalRecords)ann;
		
		Class<?>[] clazzes = setter.getParameterTypes();
		if(clazzes.length!=1){
			throw new XLSBeansException("Arguments of '" + setter.toString() + "' is invalid.");
		} else if(List.class.isAssignableFrom(clazzes[0])){
			List<?> value = createRecords(sheet, records, reader, needPostProcess);
			if(value!=null){
				setter.invoke(obj, new Object[]{value});
			}
		} else if(clazzes[0].isArray()){
			List<?> value = createRecords(sheet, records, reader, needPostProcess);
			if(value!=null){
	        	Class<?> type = clazzes[0].getComponentType();
	        	Object array = Array.newInstance(type, value.size());
	        	for(int i=0;i<value.size();i++){
	        		Array.set(array, i, value.get(i));
	        	}
				setter.invoke(obj, new Object[]{ array });
			}
		} else {
			throw new XLSBeansException("Arguments of '" + setter.toString() + "' is invalid.");
		}
	}
	
	private List<?> createRecords(Sheet sheet, HorizontalRecords records, AnnotationReader reader,
			List<NeedPostProcess> needPostProcess) throws Exception {
		
		List<Object> result = new ArrayList<Object>();
		List<HeaderInfo> headers = new ArrayList<HeaderInfo>();
		
		// get header
		int initColumn = -1;
		int initRow = -1;
		
		if(records.tableLabel().equals("")){
			initColumn = records.headerColumn();
			initRow = records.headerRow();
		} else {
			try {
				Cell labelCell = Utils.getCell(sheet, records.tableLabel(), 0);
				initColumn = labelCell.getColumn();
				initRow = labelCell.getRow() + records.bottom();
			} catch(XLSBeansException ex){
				if(records.optional()){
					return null;
				} else {
					throw ex;
				}
			}
		}
		
		int hColumn = initColumn;
		int hRow = initRow;
		int rangeCount = 1;
		
		while(true){
			try {
				Cell cell = sheet.getCell(hColumn, hRow);
				while(cell.getContents().equals("") && rangeCount < records.range()){
					cell = sheet.getCell(hColumn + rangeCount, hRow);
					rangeCount++;
				}
				if(cell.getContents().equals("")){
					break;
				}
				headers.add(new HeaderInfo(cell.getContents(), rangeCount - 1));
				hColumn++;
				rangeCount = 1;
			} catch(ArrayIndexOutOfBoundsException ex){
				break;
			}
		}
		
		// Check for columns
		RecordsProcessorUtil.checkColumns(records.recordClass(), headers, reader);
		
		RecordTerminal terminal = records.terminal();
		if(terminal==null){
			terminal = RecordTerminal.Empty;
		}

		// get records
		hRow++;
		while(hRow < sheet.getRows()){
			hColumn = initColumn;
			boolean emptyFlag = true;
			Object record = records.recordClass().newInstance();
			processMapColumns(sheet, headers, hColumn, hRow, record, reader);
			
			for(int i=0;i<headers.size() && hRow < sheet.getRows();i++){
				HeaderInfo headerInfo = headers.get(i);
				Method[] setters = Utils.getColumnMethod(record, headerInfo.getHeaderLabel(), reader);
				Cell cell = sheet.getCell(hColumn + headerInfo.getHeaderRange(), hRow);
				
				// find end of the table
				if(!cell.getContents().equals("")){
					emptyFlag = false;
				}
				if(terminal==RecordTerminal.Border && i==0){
					CellFormat format = cell.getCellFormat();
					if(format!=null && !format.getBorder(Border.LEFT).equals(BorderLineStyle.NONE)){
						emptyFlag = false;
					} else {
						emptyFlag = true;
						break;
					}
				}
				if(!records.terminateLabel().equals("")){
					if(cell.getContents().equals(records.terminateLabel())){
						emptyFlag = true;
						break;
					}
				}
				
				for(Method setter : setters){
					Cell valueCell = cell;
					Column column = reader.getAnnotation(record.getClass(), setter, Column.class);
					if(column.headerMerged() > 0){
						hColumn = hColumn + column.headerMerged();
						valueCell = sheet.getCell(hColumn, hRow);
					}
					if(valueCell.getContents().equals("")){
						CellFormat valueCellFormat = valueCell.getCellFormat();
						if(column.merged() && (valueCellFormat==null || valueCellFormat.getBorder(Border.TOP).equals(BorderLineStyle.NONE))){
							for(int k=hRow-1;k>initRow;k--){
								Cell tmpCell = sheet.getCell(hColumn, k);
								CellFormat tmpCellFormat = tmpCell.getCellFormat();
								if(tmpCellFormat!=null && !tmpCellFormat.getBorder(Border.BOTTOM).equals(BorderLineStyle.NONE)){
									break;
								}
								if(!tmpCell.getContents().equals("")){
									valueCell = tmpCell;
									break;
								}
							}
						}
					}
					if(column.headerMerged() > 0){
						hColumn = hColumn - column.headerMerged();
					}
					Utils.setPosition(hColumn, hRow, record, setter);
					Utils.invokeSetter(setter, record, valueCell.getContents());
				}
				hColumn++;
			}
			if(emptyFlag){
				break;
			}
			result.add(record);
			for(Method method : record.getClass().getMethods()){
				PostProcess ann = reader.getAnnotation(record.getClass(), method, PostProcess.class);
				if(ann!=null){
					needPostProcess.add(new NeedPostProcess(record, method));
				}
			}
			hRow++;
		}
		
		return result;
	}
	
	private void processMapColumns(Sheet sheet, List<HeaderInfo> headerInfos, 
			int begin, int row, Object record, AnnotationReader reader) throws Exception {
		
		Method[] setters = Utils.getMapColumnMethod(record, reader);
		for(Method method : setters){
			MapColumns ann = reader.getAnnotation(record.getClass(), method, MapColumns.class);
			boolean flag = false;
			Map<String, String> map = new LinkedHashMap<String, String>();
			for(HeaderInfo headerInfo : headerInfos){
				if(headerInfo.getHeaderLabel().equals(ann.previousColumnName())){
					flag = true;
					begin++;
					continue;
				}
				if(flag){
					Cell cell = sheet.getCell(begin + headerInfo.getHeaderRange(), row);
					map.put(headerInfo.getHeaderLabel(), cell.getContents());
				}
				begin++;
			}
			method.invoke(record, map);
		}
	}

}
