package edu.columbia.gemma.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Very simple class to produce String versions of any Gemma domain objects. The entire hierarchy of associations for
 * each object is printed in a tree-like format. This is primarily used for testing.
 * <hr>
 * <p>
 * Copyright (c) 2004-2005 Columbia University
 * 
 * @author pavlidis
 * @version $Id$
 */
public class PrettyPrinter {

    private PrettyPrinter() {
    }

    protected static final Log log = LogFactory.getLog( PrettyPrinter.class );

    /**
     * Print out a collection of Gemma data objects in a relatively pleasing format.
     * 
     * @param gemmaObjs Collection of objects.
     * @return String representing the objects.
     */
    public static String print( Collection<Object> gemmaObjs ) {
        StringBuffer buf = new StringBuffer();
        try {
            for ( Iterator<Object> iter = gemmaObjs.iterator(); iter.hasNext(); ) {
                Object gemmaObj = iter.next();

                if ( gemmaObj == null ) log.error( "Null object in collection" );
                print( buf, gemmaObj );

            }
        } catch ( IntrospectionException e ) {
            log.error( e, e );
        } catch ( IllegalArgumentException e ) {
            log.error( e, e );
        } catch ( IllegalAccessException e ) {
            log.error( e, e );
        } catch ( InvocationTargetException e ) {
            log.error( e, e );
        }
        return buf.toString();
    }

    /**
     * Pretty-print a single Gemma domain object.
     * 
     * @param gemmaObj
     * @return String representing the object.
     */
    public static String print( Object gemmaObj ) {
        StringBuffer buf = new StringBuffer();
        try {
            print( buf, gemmaObj );
        } catch ( IllegalArgumentException e ) {
            log.error( e, e );
        } catch ( IntrospectionException e ) {
            log.error( e, e );
        } catch ( IllegalAccessException e ) {
            log.error( e, e );
        } catch ( InvocationTargetException e ) {
            log.error( e, e );
        }
        return buf.toString();
    }

    /**
     * @param buf
     * @param gemmaObj
     * @throws IntrospectionException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private static void print( StringBuffer buf, Object gemmaObj ) throws IntrospectionException,
            IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        print( buf, gemmaObj, 0 );
    }
    
    /**
     * 
     * @param buf
     * @param gemmeCollection
     * @param level
     */
    private static void print( StringBuffer buf, Collection gemmaCollection, int level )
            throws IllegalArgumentException, IntrospectionException, IllegalAccessException, InvocationTargetException {
        for ( Object gemmaObj : gemmaCollection ) {
            print(buf, gemmaObj, level);
        }
    }
    

    /**
     * The only class that does any real work. Recursively print an object and all its associated objects.
     * 
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws IntrospectionException
     * @param buf
     * @param gemmaObj
     * @param level Used to track indents.
     */
    private static void print( StringBuffer buf, Object gemmaObj, int level ) throws IntrospectionException,
            IllegalArgumentException, IllegalAccessException, InvocationTargetException {

        if ( gemmaObj == null ) return;
        Class gemmaClass = gemmaObj.getClass();

        if ( gemmaObj instanceof Collection ) {
            print( buf, ( Collection ) gemmaObj, ++level );
            return;
        }

        if ( !gemmaClass.getName().startsWith( "edu.columbia.gemma" ) ) return;

        BeanInfo bif = Introspector.getBeanInfo( gemmaClass );
        PropertyDescriptor[] props = bif.getPropertyDescriptors();

        StringBuffer indent = new StringBuffer();
        for ( int i = 0; i < level; i++ )
            indent.append( "   " );

        boolean first = true;
        level++;

        for ( int i = 0; i < props.length; i++ ) {
            PropertyDescriptor prop = props[i];

            Object o = prop.getReadMethod().invoke( gemmaObj, new Object[] {} );

            if ( prop.getDisplayName().equals( "class" ) ) continue; // everybody has it.
            if ( prop.getDisplayName().equals( "mutable" ) ) continue; // shows up in the enums, just clutter.

            // generate a 'heading' for this object.
            if ( first ) buf.append( indent + gemmaObj.getClass().getSimpleName() + " Properties:\n" );

            first = false;
            buf.append( indent + "   " + gemmaObj.getClass().getSimpleName() + "." + prop.getName() + ": "
                    + ( o == null ? "---" : o ) + "\n" );
            print( buf, o, level );
        }
    }
}

@@ -868,25 +868,12 @@
  of 
-Gemma data object
+bean
 s in
@@ -925,24 +925,20 @@
  @param 
-gemmaObj
+bean
 s Collec
@@ -945,22 +945,20 @@
 tion of 
-object
+bean
 s.%0A     
@@ -1059,24 +1059,20 @@
 Object%3E 
-gemmaObj
+bean
 s ) %7B%0A  
@@ -1172,24 +1172,20 @@
  iter = 
-gemmaObj
+bean
 s.iterat
@@ -2861,20 +2861,16 @@
 ;%0A    %7D%0A
-    
 %0A    /**
@@ -2870,24 +2870,16 @@
     /**%0A
-     * %0A
      * @
@@ -3226,16 +3226,17 @@
   print(
+ 
 buf, gem
@@ -3247,16 +3247,17 @@
 j, level
+ 
 );%0A     
@@ -3266,21 +3266,16 @@
  %7D%0A    %7D
-%0A    
 %0A%0A    /*
@@ -3696,24 +3696,20 @@
  Object 
-gemmaObj
+bean
 , int le
@@ -3845,32 +3845,28 @@
        if ( 
-gemmaObj
+bean
  == null ) r
@@ -3899,24 +3899,20 @@
 Class = 
-gemmaObj
+bean
 .getClas
@@ -3930,24 +3930,20 @@
    if ( 
-gemmaObj
+bean
  instanc
@@ -4000,24 +4000,20 @@
 ction ) 
-gemmaObj
+bean
 , ++leve
@@ -4589,24 +4589,20 @@
 invoke( 
-gemmaObj
+bean
 , new Ob
@@ -4916,24 +4916,20 @@
 ndent + 
-gemmaObj
+bean
 .getClas
@@ -5039,24 +5039,20 @@
 %22   %22 + 
-gemmaObj
+bean
 .getClas

make names more generic