/*
	Copyright (c) 2009-2011 Olivier Chafik, All Rights Reserved
	
	This file is part of JNAerator (http://jnaerator.googlecode.com/).
	
	JNAerator is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	JNAerator is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.ochafik.lang.reflect;

import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

import com.ochafik.io.StringBufferOutputStream;
import com.ochafik.lang.AssertUtils;

public class DebugUtils {

	public static abstract class FieldAccessor {
		public abstract Object access(Field f, Object target) throws IllegalArgumentException, IllegalAccessException;
		//public Object access(Field f, Object target) throws IllegalArgumentException, IllegalAccessException {
		//return f.get(target);
		//}
	}

	public static final void print(Object o, FieldAccessor accessor) {
		print(o, System.out, false, false, "", accessor);
	}

	public static final void printErr(Object o, FieldAccessor accessor) {
		print(o, System.err, false, false, "", accessor);
	}

	public static final void println(Object o, FieldAccessor accessor) {
		print(o, System.out, true, false, "", accessor);
	}

	public static final void printlnErr(Object o, FieldAccessor accessor) {
		print(o, System.err, true, false, "", accessor);
	}

	public static final void print(Object o) {
		print(o, System.out, false, false, "", null);
	}

	public static final void printErr(Object o) {
		print(o, System.err, false, false, "", null);
	}

	public static final void println(Object o) {
		print(o, System.out, true, false, "", null);
	}
	public static final void println(Object o, PrintStream out) {
		print(o, out, true, false, "", null);
	}

	public static final void printlnErr(Object o) {
		print(o, System.err, true, false, "", null);
	}

	protected static String escape(String s) {
		return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t").replace("\r", "");
	}
	public static final void printAsCharSequence(CharSequence o, PrintStream out, boolean lines, boolean startIndent, String indent, FieldAccessor accessor) {
		out.print('"');
		out.print(escape(((CharSequence)o).toString()));
		out.print('"');
		if (lines) out.println();
	}

	public static final void printStructureInsides(Object o, PrintStream out, boolean lines, boolean startIndent, String indent, FieldAccessor accessor) {
		Class<?> type = o.getClass();
		Set<Field> fields = getFields(type);
		int i = 0;
		for (Field f : fields) {
			boolean inaccessibleValue = false;
			Object v;
			try {
				//f.isAccessible()
				v = accessor == null ? f.get(o) : accessor.access(f, o);
			} catch (IllegalAccessException ex) {
				// field is private and from a different package than the accessor ! Try using a getter
				String fn = f.getName();
				try {
					Method m = type.getMethod("get" + fn.substring(0,1).toUpperCase() + fn.substring(1), new Class[0]);
					v = m.invoke(o, new Object[0]);
				} catch (Exception e) {
					inaccessibleValue = true;
					v = null;
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			if (lines) {
				out.print(indent);
			} else if (i++ != 0) out.print("; ");

			out.print(f.getName());
			out.print(" = ");
			if (inaccessibleValue) {
				out.print('?');
			} else {
				print(v, out, lines, false, indent, accessor);
			}
		}
	}

	public static final void print(Object o, PrintStream out, boolean lines, boolean startIndent, String indent, FieldAccessor accessor) {
		if (lines && startIndent) out.print(indent);

		if (o == null) {
			out.print(o);
			if (lines) out.println();
			return;
		} 
		Class<?> type = o.getClass();
		boolean isCollection = o instanceof Collection<?>;

		if (o instanceof CharSequence) {
			out.print('"');
			out.print(escape(o.toString()));
			out.print('"');
			if (lines) out.println();
			return;
		} else if (type == Character.class) {
			out.print('\'');
			out.print(escape(o.toString()));
			out.print('\'');
			if (lines) out.println();
			return;
		} else if (!isCollection && hasToStringMethod(type)) {
			out.print(o);
			if (lines) out.println();
			return;
		}
		out.print(type.getSimpleName());
		out.print(" {");
		if (lines) out.println();

		String newIndent = indent + "\t";

		if (type.isArray()) {
			for (int i = 0, n = Array.getLength(o); i < n; i++) {
				if (!lines && i != 0) out.print(", ");
				print(Array.get(o, i), out, lines, true, newIndent, accessor);
			}
		} else if (isCollection) {
			int i = 0;
			for (Object e : (Collection<?>)o) {
				if (!lines && i++ != 0) out.print(", ");
				print(e, out, lines, true, newIndent, accessor);
			}
		} else {
			printStructureInsides(o, out, lines, startIndent, newIndent, accessor);
		}
		if (lines) out.print(indent);
		out.print("}");
		if (lines) out.println();
	}

	public static Set<Field> getFields(Class<?> type) {
		Set<Field> fields = new TreeSet<Field>(new Comparator<Field>() { public int compare(Field o1, Field o2) { 
			return o1.getName().compareTo(o2.getName()); 
		}});
		fields.addAll(Arrays.asList(type.getFields()));
		do {
			fields.addAll(Arrays.asList(type.getDeclaredFields()));
		} while ((type = type.getSuperclass()) != null);
		return fields;
	}
	protected static void getFields_aux(Class<?> type, Set<Field> fields) {
		
	}
	
	public static boolean hasToStringMethod(Class<?> c) {
		if (c == Object.class) return false;
		for (Method m : c.getDeclaredMethods()) {
			if (m.getName().equals("toString") && m.getParameterTypes().length == 0) {
				return true;
			}
		}
		return hasToStringMethod(c.getSuperclass());
	}

	public static void main(String[] args) {
		//print(new int[] { 1, 2, 3, 4});
		println(new Object[] { new AssertUtils.Test(), }, new FieldAccessor() {
			public Object access(Field f, Object target) throws IllegalArgumentException, IllegalAccessException {
				return f.get(target);
			}
		});
	}

	public static String toString(Object object) {
		StringBufferOutputStream out = new StringBufferOutputStream();
		print(object, new PrintStream(out), false, false,"", null);
		return out.toString();
	}

}
