package net.java.amateras.xlsbeans.processor;

import java.lang.annotation.Annotation;
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.MapColumns;
import net.java.amateras.xlsbeans.annotation.PostProcess;
import net.java.amateras.xlsbeans.annotation.RecordTerminal;
import net.java.amateras.xlsbeans.annotation.VerticalRecords;
import net.java.amateras.xlsbeans.xml.AnnotationReader;

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

	public void doProcess(Sheet sheet, Object obj, Method setter,
			Annotation ann, AnnotationReader reader,
			List<NeedPostProcess> needPostProcess) throws Exception {
		
		VerticalRecords records = (VerticalRecords)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(Object[].class.isAssignableFrom(clazzes[0])){
			List value = createRecords(sheet, records, reader, needPostProcess);
			if(value!=null){
				setter.invoke(obj, new Object[]{value.toArray()});
			}
		} else {
			throw new XLSBeansException("Arguments of '" + setter.toString() + "' is invalid.");
		}
	}
	
	private List createRecords(Sheet sheet, VerticalRecords 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() + 1;
				initRow = labelCell.getRow();
			} 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, hRow + rangeCount);
					rangeCount++;
				}
				if(cell.getContents().equals("")){
					break;
				} else {
					for(int j=hColumn;j>initColumn;j--){
						Cell tmpCell = sheet.getCell(j, hRow);
						if(!tmpCell.getContents().equals("")){
							cell = tmpCell;
							break;
						}
					}
				}
				headers.add(new HeaderInfo(cell.getContents(), rangeCount-1));
				hRow++;
				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
		hColumn++;
		while(hColumn < sheet.getColumns()){
			hRow = initRow;
			boolean emptyFlag = true;
			Object record = records.recordClass().newInstance();
			processMapColumns(sheet, headers, hRow, hColumn, record, reader);
			
			for(int i=0;i<headers.size() && hColumn < sheet.getColumns();i++){
				HeaderInfo headerInfo = headers.get(i);
				Method[] setters = Utils.getColumnMethod(record, headerInfo.getHeaderLabel(), reader);
				Cell cell = sheet.getCell(hColumn, hRow + headerInfo.getHeaderRange());
				
				// 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.TOP).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){
						hRow = hRow + column.headerMerged();
						valueCell = sheet.getCell(hColumn, hRow);
					}
					if(valueCell.getContents().equals("")){
						CellFormat valueCellFormat = valueCell.getCellFormat();
						if(column.merged() && (valueCellFormat==null && valueCellFormat.getBorder(Border.RIGHT).equals(BorderLineStyle.NONE))){
							for(int k=hColumn;k>initColumn;k--){
								Cell tmpCell = sheet.getCell(k, hRow);
								CellFormat tmpCellFormat = tmpCell.getCellFormat();
								if(tmpCellFormat!=null && !tmpCellFormat.getBorder(Border.LEFT).equals(BorderLineStyle.NONE)){
									break;
								}
								if(!tmpCell.getContents().equals("")){
									valueCell = tmpCell;
									break;
								}
							}
						}
					}
					if(column.headerMerged() > 0){
						hRow = hRow - column.headerMerged();
					}
					Utils.invokeSetter(setter, record, valueCell.getContents());
				}
				hRow++;
			}
			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));
				}
			}
			hColumn++;
		}
		
		return result;
	}
	
	private void processMapColumns(Sheet sheet, List<HeaderInfo> headerInfos, 
			int begin, int column, 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(column, begin + headerInfo.getHeaderRange());
					map.put(headerInfo.getHeaderLabel(), cell.getContents());
				}
				begin++;
			}
			method.invoke(record, map);
		}
	}

}
