package net.java.amateras.xlsbeans.sandbox;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.java.amateras.xlsbeans.annotation.Column;
import net.java.amateras.xlsbeans.annotation.HorizontalRecords;
import net.java.amateras.xlsbeans.annotation.IterateTables;
import net.java.amateras.xlsbeans.annotation.LabelledCell;
import net.java.amateras.xlsbeans.annotation.MapColumns;
import net.java.amateras.xlsbeans.annotation.VerticalRecords;

/**
 * 
 * @author Mitsuyoshi Hasegawa
 */
public class DumpXLSBeans {

    // 現在の階層
    private StringBuilder indent = new StringBuilder();
    
    private String getIndent() {
        return this.indent.toString();
    }
    
    /**
     * XLSBeans 形式の POJO に対してダンプを表示する。
     * 
     * @param bean
     * @throws Exception
     */
    public static void showDump(Object bean) throws Exception {
        DumpXLSBeans dumpXlsBeans = new DumpXLSBeans();
        dumpXlsBeans.dump(bean, dumpXlsBeans.getIndent());
    }
    
    private void dump(Object bean, String indent) throws Exception {
        this.indent.append(indent);
        this.indent.append("    ");
        
        if (bean == null) {
            System.out.println(getIndent() + "bean is null");
            return;
        }
        
        
        // Getter を拾い出す。
        Method[] getters = getGetter(bean);
        if (getters.length == 0) {
            return;
        }
        
        // Beanクラス名表示
        System.out.println(getIndent() + "***************** <<" + bean.getClass().getName() + ">>");
        
        for (Method getter : getters) {
            if (!isArrayOrListReturnValue(getter)) {
                // 単項目
                Method setter = getSetterInBean(bean, getter);
                String label = getAnnotationLabel(setter);
                Object obj = getter.invoke(bean, new Object[]{});
                System.out.println(getIndent() + label + "[" + obj + "]");
            } else {
                if (getter.getReturnType().isAssignableFrom(List.class)) {
                    // リスト
                    List list = (List) getter.invoke(bean, new Object[]{});
                    if (list == null) {
                        System.out.println(getIndent() + "################ " + getter.getName() + "method return value(List) is null.");
                        continue;
                    }
                    for (Object obj : list) {
                        (new DumpXLSBeans()).dump(obj, getIndent());
                    }
                } else if (getter.getReturnType().isArray()) {
                    // 配列
                    Object array = getter.invoke(bean, new Object[]{});
                    if (array == null) {
                        System.out.println(getIndent() + "################ " +  getter.getName() + "method return value(Array) is null.");
                        continue;
                    }
                    Object[] arrays = (Object[]) array;
                    for (Object obj : arrays) {
                        (new DumpXLSBeans()).dump(obj, getIndent());
                    }
                } else if (getter.getReturnType().isAssignableFrom(Map.class)) {
                    // マップ
                    Map map = (Map) getter.invoke(bean, new Object[]{});
                    if (map == null) {
                        System.out.println(getIndent() + "*****************" +  getter.getName() + " return value(Map) is null.");
                        continue;
                    }
                    System.out.print(getIndent() + "{");
                    for (Object obj : map.keySet()) {
                        System.out.print("[" + obj + ", " + map.get(obj) + "]");
                    }
                    System.out.println("}");
                    continue;
                } else {
                    // 型がおかしいかも
                    System.err.println(getIndent() + "*****************" +  "may be getter is invalid.");
                }
            }
        }
    }
    
    private Method getSetterInBean(Object bean, Method getter) throws Exception {
        String getterName = getter.getName();
        String guessSetterName = getterName.replace("get", "set");
        Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            if (method.getName().equals(guessSetterName)) {
                return method;
            }
        }
        return null;
    }
    
    private String getAnnotationLabel(Method setter) {
        if (setter == null) {
            return "";
        }
        if (setter.isAnnotationPresent(Column.class)) {
            Column column = setter.getAnnotation(Column.class);
            return column.columnName();
        } else if (setter.isAnnotationPresent(HorizontalRecords.class)) {
            HorizontalRecords records = setter.getAnnotation(HorizontalRecords.class);
            return records.tableLabel();
        } else if (setter.isAnnotationPresent(LabelledCell.class)) {
            LabelledCell cell = setter.getAnnotation(LabelledCell.class);
            return cell.label();
        } else if (setter.isAnnotationPresent(MapColumns.class)) {
            MapColumns mapColumns = setter.getAnnotation(MapColumns.class);
            return mapColumns.previousColumnName();
        } else if (setter.isAnnotationPresent(VerticalRecords.class)) {
            VerticalRecords records = setter.getAnnotation(VerticalRecords.class);
            return records.tableLabel();
        } else if (setter.isAnnotationPresent(IterateTables.class)) {
            IterateTables tables = setter.getAnnotation(IterateTables.class);
            return tables.tableLabel();
        }
        return null;
    }
    
    private boolean isArrayOrListReturnValue(Method getter) {
        if (getter == null) {
            return false;
        }
        Class<?> type = getter.getReturnType();
        if (Void.class.isAssignableFrom(type)) {
            return false;
        }
        
        if (List.class.isAssignableFrom(type)) {
            return true;
        }
        if (Map.class.isAssignableFrom(type)) {
            return true;
        }
        if (type.isArray()) {
            return true;
        }
        // 配列でもListでもない
        return false;
        
    }
    
    private Method[] getGetter(Object bean) throws Exception {
        Method[] methods = bean.getClass().getMethods();
        if (methods == null || methods.length == 0) {
            return new Method[]{};
        }
        
        if (bean instanceof String) {
            return new Method[]{};
        }
        
        List<Method> list = new ArrayList<Method>();
        for (Method msd : methods) {
            String methodName = msd.getName();
            if (methodName.startsWith("get") && !"getClass".equals(methodName)
                    && !"getBytes".equals(methodName)) {
                list.add(msd);
            }
        }
        return list.toArray(new Method[]{});
    }
    
}
