package hirondelle.web4j.database;

import java.util.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.ResultSetMetaData;
import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.util.Args;

/**
 Translates a <tt>ResultSet</tt> row into a <tt>Map</tt>.
 
 <P>The returned {@code Map<String, Object>} has : 
 <ul>
 <li>key - column name.
 <li>value - column value as an unformatted object. The class of each object is controlled 
 by the class array passed to the constructor.
 </ul>
*/
final class ReportBuilderUnformatted extends ModelBuilder<Map<String, Object>> {
  
  /**
   Constructor for applying some processing to raw column values.
  
   <P><tt>aColumnTypes</tt> is used to convert each column into an <tt>Object</tt>.
   The supported types are the same as for {@link ConvertColumn}.
   
   @param aColumnTypes defines the type of each column, in order from left to right, as 
   they appear in the <tt>ResultSet</tt>; contains <tt>N</tt> class literals, 
   where <tt>N</tt> is the number of columns in the <tt>ResultSet</tt>; contains only 
   the classes supported by {@link ConvertColumn}; this constructor will create a defensive 
   copy of this parameter. 
  */
  ReportBuilderUnformatted(Class<?>[] aColumnTypes){
    Args.checkForPositive(aColumnTypes.length);
    fColumnTypes = defensiveCopy(aColumnTypes);
    fColumnToObject = BuildImpl.forConvertColumn();
  }
  
  /**
   Returns an unmodifiable <tt>Map</tt>.
  
   <P>See class constructor.
  */
  Map<String, Object> build(ResultSet aRow) throws SQLException {
    LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
    Object value = null;
    ResultSetMetaData metaData = aRow.getMetaData();
    for (int columnIdx = 1; columnIdx <= metaData.getColumnCount(); ++columnIdx) {
      if ( fColumnTypes.length != metaData.getColumnCount() ) {
        throw new IllegalArgumentException(
          "Class[] has different number of elements than ResultSet: " + fColumnTypes.length + 
          " versus " + metaData.getColumnCount () + ", respectively."
        );
      }
      value = getUnformattedObject(aRow, columnIdx);
      result.put(metaData.getColumnName(columnIdx), value);
    }
    return Collections.unmodifiableMap(result);
  }
  
  // PRIVATE //

  /** The types to which ResultSet columns will be converted.  */
  private Class<?>[] fColumnTypes; 
  
  /** Translates ResultSet columns into desired Objects. */
  private ConvertColumn fColumnToObject;
  
  private Class[] defensiveCopy(Class[] aClassArray){
    Class[] result = new Class[aClassArray.length];
    System.arraycopy(aClassArray, 0, result, 0, aClassArray.length);
    return result;
  }
  
  private Object getUnformattedObject(ResultSet aRow, int aColumnIdx) throws SQLException {
    return fColumnToObject.convert(aRow, aColumnIdx, fColumnTypes[aColumnIdx-1]);
  }
}
