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.List;

import jxl.Cell;
import jxl.Sheet;
import net.java.amateras.xlsbeans.NeedPostProcess;
import net.java.amateras.xlsbeans.Utils;
import net.java.amateras.xlsbeans.XLSBeansException;
import net.java.amateras.xlsbeans.annotation.HorizontalRecords;
import net.java.amateras.xlsbeans.annotation.HorizontalRecordsForIterateTable;
import net.java.amateras.xlsbeans.annotation.IterateTables;
import net.java.amateras.xlsbeans.annotation.LabelledCell;
import net.java.amateras.xlsbeans.annotation.LabelledCellForIterateTable;
import net.java.amateras.xlsbeans.xml.AnnotationReader;

/**
 * 
 * @author Mitsuyoshi Hasegawa
 * @see net.java.amateras.xlsbeans.annotation.IterateTables
 */
public class IterateTablesProcessor implements FieldProcessor {

    public void doProcess(Sheet sheet, Object obj, Method setter,
            Annotation ann, AnnotationReader reader,
            List<NeedPostProcess> needPostProcess) throws Exception {
        
        IterateTables iterateTables = (IterateTables) ann;
        
        Class<?>[] setterArgClzArray = setter.getParameterTypes();
        if (setterArgClzArray.length != 1) {
            // isn't setter attribute
            throw new XLSBeansException(
            		"Arguments of '" + setter.toString() + "' is invalid.");
        }
        
        // create multi-table objects.
        List<?> value = createTables(sheet, iterateTables, reader, needPostProcess);
        if (List.class.isAssignableFrom(setterArgClzArray[0])) {
            setter.invoke(obj, new Object[]{value});
            
        } else if (setterArgClzArray[0].isArray()) {
        	Class<?> type = setterArgClzArray[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.");
        }
    }
    
    /**
     * Excel テーブルオブジェクトのリストを取得する。
     * 
     * @param sheet シートオブジェクト
     * @param iterateTables テーブルアノテーション
     * @param reader AnnotationReader オブジェクト
     * @param needPostProcess 後処理
     * @return テーブルオブジェクト
     * @throws Exception 何らかの例外
     */
    protected List<?> createTables(Sheet sheet, IterateTables iterateTables ,
            AnnotationReader reader,  List<NeedPostProcess> needPostProcess)
            throws Exception {
        List<Object> resultTableList = new ArrayList<Object>();
        
        Cell after = null;
        Cell currentCell = null;
        String label = iterateTables.tableLabel();
        
        while ((currentCell = Utils.getCell(sheet, label, after, false, false)) != null) {
            // 1 table object instance
            Object tableObj = iterateTables.tableClass().newInstance();
            
            // process single label.
            processSingleLabelledCell(sheet, tableObj, currentCell, reader,
                    needPostProcess);
            // process horizontal table.
            processMultipleTableCell(sheet, tableObj, currentCell, reader,
                    iterateTables, needPostProcess);
            
            // TODO process vertical table
            
            resultTableList.add(tableObj);
            after = currentCell;
        }
        return resultTableList;
    }
    
    /**
     * 単体セルに対して setter を呼び出し、JavaBeans へのマッピングを行う。
     * 
     * @param sheet シートオブジェクト
     * @param tableObj マッピング先の JavaBeans 
     * @param headerCell テーブルヘッダセル
     * @param reader AnnotationReader オブジェクト
     * @param needPostProcess 後処理設定
     * @throws Exception 何らかの例外
     */
    protected void processSingleLabelledCell(Sheet sheet, Object tableObj,
            Cell headerCell, AnnotationReader reader,
            List<NeedPostProcess> needPostProcess) throws Exception {
        Method[] setters = Utils.getMethodWithAnnotation(
                tableObj, reader, LabelledCell.class);
        LabelledCellProcessor labelledCellProcessor = new LabelledCellProcessor();
        for (Method setter : setters) {
            LabelledCell ann = setter.getAnnotation(LabelledCell.class);
            Cell titleCell = null;
            try {
                titleCell = Utils.getCell(sheet, ann.label(), headerCell);
            } catch (XLSBeansException e) {
                if (ann.optional()) {
                    continue;
                } else {
                    throw e;
                }
            }
            
            LabelledCell labelledCell = new LabelledCellForIterateTable(ann,
                    titleCell.getRow(), titleCell.getColumn());
            
            labelledCellProcessor.doProcess(sheet, tableObj,
                    setter, labelledCell, reader, needPostProcess);
        }
    }
    
    /**
     * Excel 内の 1テーブルのレコードに対して setter を呼び出し、JavaBeans への
     * マッピングを行う。
     * 
     * @param sheet シートオブジェクト
     * @param tableObj マッピング先のJavaBeans 
     * @param headerCell テーブルヘッダセル
     * @param reader AnnotationReader オブジェクト
     * @param needPostProcess 後処理設定
     * @throws Exception 何らかの例外
     */
    protected void processMultipleTableCell(Sheet sheet, Object tableObj,
            Cell headerCell, AnnotationReader reader,
            IterateTables iterateTables,
            List<NeedPostProcess> needPostProcess) throws Exception {
        
        Method[] setters = Utils.getMethodWithAnnotation(
                tableObj, reader, HorizontalRecords.class);
        
        
        
        int headerColumn = headerCell.getColumn();
        int headerRow = headerCell.getRow();
        
        if (iterateTables.bottom() > 0) {
            // if positive value set to bottom(), row index of table header move
            headerRow += iterateTables.bottom();
        }
        
        HorizontalRecordsProcessor processor = new HorizontalRecordsProcessor();
        for (Method setter : setters) {
            HorizontalRecords ann = setter.getAnnotation(HorizontalRecords.class);
            if (iterateTables.tableLabel().equals(ann.tableLabel())) {
                // 処理対象と同じテーブルラベルのとき、マッピングを実行する。
                HorizontalRecords records = new HorizontalRecordsForIterateTable(
                        ann,headerColumn, headerRow);
                // horizontal record-mapping
                processor.doProcess(sheet, tableObj, setter, records,
                        reader, needPostProcess);
            }
        }
    }
}
