package junkutil.common;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import junkutil.set.LzList;

/**
 * オブジェクトをダンプ表示する
 * @author Hiroyuki Shiota
 */
public class DataDumper {
	/**
	 * コンストラクタ
	 */
	private DataDumper() {
	}

	/**
	 * Mapをダンプする
	 * @param out
	 * @param map
	 * @throws IOException
	 * @see Config#dumpMap(PrintWriter, Map)
	 */
	public static void dumpMap(PrintStream out, Map map) {
		dumpMap(new PrintWriter(out), map);
	}

	/**
	 * Mapをダンプする
	 * @param out
	 * @param map
	 * @throws IOException
	 */
	public static void dumpMap(PrintWriter out, Map map) {
		List keys = new LzList(map.keySet()).sortAsc().list();
		for (Iterator i = keys.iterator(); i.hasNext(); ) {
			String key = (String)i.next();
			Object value = map.get(key);
			if (value instanceof Object[]) {
				List array = Arrays.asList((Object[])value);
				out.println(key + "=" + array.toString());
			} else {
				out.println(key + "=" + value.toString());
			}
		}
		out.flush();
	}

	/**
	 * Listをダンプする
	 * @param out
	 * @param list
	 * @throws IOException
	 */
	public static void dumpList(PrintStream out, List list) {
		dumpList(new PrintWriter(out), list);
	}

	/**
	 * Listをダンプする
	 * @param out
	 * @param list
	 */
	public static void dumpList(PrintWriter out, List list) {
		for (int i = 0; i < list.size(); i++) {
			Object obj = list.get(i);
			if (obj instanceof Object[]) {
				List array = Arrays.asList((Object[])obj);
				out.println(array.toString());
			} else {
				out.println(obj.toString());
			}
		}
		out.flush();
	}

	/**
	 * byteの配列をダンプする
	 * @param out
	 * @param byteArray
	 */
	public static void dumpByteArray(PrintStream out, byte[] byteArray) {
		dumpByteArray(new PrintWriter(out), byteArray);
	}

	/**
	 * byteの配列をダンプする
	 * @param out
	 * @param byteArray
	 */
	public static void dumpByteArray(PrintWriter out, byte[] byteArray) {
		//HexDumpEncoder hexdumpencoder = new HexDumpEncoder();
		//out.println(hexdumpencoder.encodeBuffer(byteArray));
		int no = 0x0000;
		BLOCK: for (int i = 0; i < byteArray.length; i++) {
			for (int j = 0; j < 16; j++) {
				if (i*16+j >= byteArray.length) break BLOCK;
				if (j==0) {
					String hex =  Integer.toHexString(no).toUpperCase();
					out.print("0000".substring(0, 4-hex.length()) + hex + ":");
					no+=16;
				}
				if (j==8) out.print("  ");
				String hex = Integer.toHexString(0xFF & byteArray[i*16+j]).toUpperCase();
				out.print(" "+(hex.length()==1 ? "0" : "")+hex);
			}
			out.println();
		}
		out.println();
		out.flush();
	}

	/**
	 * 文字列の配列をダンプする
	 * @param out
	 * @param strings
	 */
	public static void dumpStrings(PrintStream out, String[] strings) {
		dumpStrings(new PrintWriter(out), strings);
	}

	/**
	 * 文字列の配列をダンプする
	 * @param out
	 * @param strings
	 */
	public static void dumpStrings(PrintWriter out, String[] strings) {
		for (int i = 0; i < strings.length; i++) {
			out.println(strings[i]);
		}
		out.flush();
	}

	/**
	 * 文字列をダンプする
	 * @param out
	 * @param strings
	 */
	public static void dumpString(PrintStream out, String string) {
		dumpString(new PrintWriter(out), string);
	}

	/**
	 * 文字列をダンプする
	 * @param out
	 * @param strings
	 */
	public static void dumpString(PrintWriter out, String string) {
		out.println(string);
		out.flush();
	}

	/**
	 * 例外をダンプする
	 * @param out
	 * @param t
	 */
	public static void dumpException(PrintStream out, Throwable t) {
		dumpException(new PrintWriter(out), t);
	}

	/**
	 * 例外をダンプする
	 * @param out
	 * @param t
	 */
	public static void dumpException(PrintWriter out, Throwable t) {
		out.println(exceptionToString(t));
		out.flush();
	}

	/**
	 * 例外を文字列に変える
	 * @param e
	 * @return
	 */
	public static String exceptionToString(Throwable t) {
		if (t==null) return "null";
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		PrintStream out = new PrintStream(bout);
		t.printStackTrace(out);
		return bout.toString();
	}

	/**
	 * Beanをダンプする。
	 * 具体的には、メソッド名がgetで始まる引数の無いメソッドすべてを順に呼び、メソッド名と戻り値と戻り値の型を書き出す。
	 * @param out
	 * @param obj
	 */
	public static void dumpBean(PrintWriter out, Object obj) {
		dumpBean(DataDumper.wrap(out), obj);
	}

	/**
	 * Beanをダンプする。
	 * 具体的には、メソッド名がgetで始まる引数の無いメソッドすべてを順に呼び、メソッド名と戻り値と戻り値の型を書き出す。
	 * @param out
	 * @param obj
	 */
	public static void dumpBean(PrintStream out, Object obj) {
		if (out == null) out = System.out;
		if (obj == null) throw new NullPointerException("obj is null.");
		Class objClass = obj.getClass();

		out.println("[" + objClass.getName() + "] Dump Start");
		Method [] methods = objClass.getMethods();
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			String methodName = method.getName();
			Class returnType = method.getReturnType();
			String returnTypeStr = getClassName(returnType);
			Class [] params = method.getParameterTypes();

			if (returnTypeStr.equals("void")
				|| returnTypeStr.equals("java.lang.Class")) continue;
			if (!methodName.startsWith("get")) continue;
			if (params.length > 0) continue;

			out.print(methodName + "()=");
			Object rtObj = null;
			String rtObjStr = null;
			try {
				rtObj = method.invoke(obj, new Object[]{});
				if (rtObj != null && rtObj.getClass().isArray()) {
					if (rtObj instanceof boolean[]) {
						boolean[] pa = (boolean[])rtObj;
						Boolean[] wa = new Boolean[pa.length];
						for (int j = 0; j < pa.length; j++) wa[j] = Boolean.valueOf(pa[j]);
						rtObj = Arrays.asList(wa);
					} else if (rtObj instanceof byte[]) {
						byte[] pa = (byte[])rtObj;
						Byte[] wa = new Byte[pa.length];
						for (int j = 0; j < pa.length; j++) wa[j] = Byte.valueOf(pa[j]);
						rtObj = Arrays.asList(wa);
					} else if (rtObj instanceof int[]) {
						int[] pa = (int[])rtObj;
						Integer[] wa = new Integer[pa.length];
						for (int j = 0; j < pa.length; j++) wa[j] = Integer.valueOf(pa[j]);
						rtObj = Arrays.asList(wa);
					} else if (rtObj instanceof float[]) {
						float[] pa = (float[])rtObj;
						Float[] wa = new Float[pa.length];
						for (int j = 0; j < pa.length; j++) wa[j] = Float.valueOf(pa[j]);
						rtObj = Arrays.asList(wa);
					} else if (rtObj instanceof double[]) {
						double[] pa = (double[])rtObj;
						Double[] wa = new Double[pa.length];
						for (int j = 0; j < pa.length; j++) wa[j] = Double.valueOf(pa[j]);
						rtObj = Arrays.asList(wa);
					} else {
						rtObj = Arrays.asList((Object[])rtObj);
					}
				}
				rtObjStr = (rtObj == null) ? "null" : rtObj.toString();
			} catch (InvocationTargetException e) {
				if (e.getCause()!=null) {
					out.println("(exception):" + e.getCause().getMessage() + " " + returnTypeStr);
				} else {
					out.println("(exception):" + e.getMessage() + " " + returnTypeStr);
				}
				continue;
			} catch (IllegalAccessException e) {
				if (e.getCause()!=null) {
					out.println("(exception):" + e.getCause().getMessage() + " " + returnTypeStr);
				} else {
					out.println("(exception):" + e.getMessage() + " " + returnTypeStr);
				}
				continue;
			}
			if (rtObjStr == null) rtObjStr = "null";
			out.println(rtObjStr + " " + returnTypeStr);
		}
		out.println("[" + objClass.getName() + "] Dump End");
		out.flush();
	}

	/**
	 * クラス名を取得する。
	 * Stringの配列の文字列表現を、人間が読みやすい表現に変える。
	 * @param argClass
	 * @return
	 */
	private static String getClassName(Class argClass) {
		if (argClass.isArray()) {
			if (argClass == boolean[].class) return "boolean[]";
			if (argClass == byte[].class) return "byte[]";
			if (argClass == int[].class) return "int[]";
			if (argClass == float[].class) return "float[]";
			if (argClass == double[].class) return "double[]";
			return argClass.getName().replaceAll("^\\[L|;", "") + "[]";
		} else {
			return argClass.getName();
		}
	}

	/**
	 * JspWriterなどの使い辛いWriterクラスをPrintWriterで包む
	 * @param jw
	 * @return
	 */
	public static PrintWriter wrap(Writer jw) {
		return new PrintWriter(jw);
	}
}
