/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2009 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Contributors:
 * S. Brett Sutton - bsutton@idatam.com.au
 */
package net.sf.jasperreports.engine;

import java.awt.Image;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

import net.sf.jasperreports.engine.util.JRImageLoader;


/**
 * An implementation of a data source that uses a supplied <tt>ResultSet</tt>.
 * @author Teodor Danciu (teodord@users.sourceforge.net)
 * @version $Id: JRResultSetDataSource.java 3676 2010-04-02 09:35:07Z shertage $
 */
public class JRResultSetDataSource implements JRDataSource
{


	private static final String INDEXED_COLUMN_PREFIX = "COLUMN_";
	private static final int INDEXED_COLUMN_PREFIX_LENGTH = INDEXED_COLUMN_PREFIX.length();
	
	/**
	 *
	 */
	private ResultSet resultSet = null;
	private Map columnIndexMap = new HashMap();
	

	/**
	 *
	 */
	public JRResultSetDataSource(ResultSet rs)
	{
		resultSet = rs;
	}


	/**
	 *
	 */
	public boolean next() throws JRException
	{
		boolean hasNext = false;
		
		if (resultSet != null)
		{
			try
			{
				hasNext = resultSet.next();
			}
			catch (SQLException e)
			{
				throw new JRException("Unable to get next record.", e);
			}
		}
		
		return hasNext;
	}


	/**
	 *
	 */
	public Object getFieldValue(JRField field) throws JRException
	{
		Object objValue = null;

		if (field != null && resultSet != null)
		{
			Integer columnIndex = getColumnIndex(field.getName());
			Class clazz = field.getValueClass();

			try
			{
				if (clazz.equals(java.lang.Boolean.class))
				{
					objValue = resultSet.getBoolean(columnIndex.intValue()) ? Boolean.TRUE : Boolean.FALSE;
				}
				else if (clazz.equals(java.lang.Byte.class))
				{
					objValue = new Byte(resultSet.getByte(columnIndex.intValue()));
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (
					clazz.equals(java.util.Date.class)
					|| clazz.equals(java.sql.Date.class)
					)
				{
					objValue = resultSet.getDate(columnIndex.intValue());
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.sql.Timestamp.class))
				{
					objValue = resultSet.getTimestamp(columnIndex.intValue());
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.sql.Time.class))
				{
					objValue = resultSet.getTime(columnIndex.intValue());
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.lang.Double.class))
				{
					objValue = new Double(resultSet.getDouble(columnIndex.intValue()));
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.lang.Float.class))
				{
					objValue = new Float(resultSet.getFloat(columnIndex.intValue()));
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.lang.Integer.class))
				{
					objValue = Integer.valueOf(resultSet.getInt(columnIndex.intValue()));
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.io.InputStream.class))
				{
					byte[] bytes = readBytes(columnIndex);
					
					if(bytes == null)
					{
						objValue = null;
					}
					else
					{
						objValue = new ByteArrayInputStream(bytes);
					}					
				}
				else if (clazz.equals(java.lang.Long.class))
				{
					objValue = new Long(resultSet.getLong(columnIndex.intValue()));
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.lang.Short.class))
				{
					objValue = new Short(resultSet.getShort(columnIndex.intValue()));
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.math.BigDecimal.class))
				{
					objValue = resultSet.getBigDecimal(columnIndex.intValue());
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(java.lang.String.class))
				{
					int columnType = resultSet.getMetaData().getColumnType(columnIndex.intValue());
					switch (columnType)
					{
						case Types.CLOB:
							Clob clob = resultSet.getClob(columnIndex.intValue());
							if (resultSet.wasNull())
							{
								objValue = null;
							}
							else
							{
								objValue = clobToString(clob);
							}
							break;
							
						default:
							objValue = resultSet.getString(columnIndex.intValue());
							if(resultSet.wasNull())
							{
								objValue = null;
							}
							break;
					}
				}
				else if (clazz.equals(Clob.class))
				{
					objValue = resultSet.getClob(columnIndex.intValue());
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(Reader.class))
				{
					Reader reader = null;
					long size = -1;
					
					int columnType = resultSet.getMetaData().getColumnType(columnIndex.intValue());
					switch (columnType)
					{
						case Types.CLOB:
							Clob clob = resultSet.getClob(columnIndex.intValue());
							if (!resultSet.wasNull())
							{
								reader = clob.getCharacterStream();
								size = clob.length();
							}
							break;
							
						default:
							reader = resultSet.getCharacterStream(columnIndex.intValue());
							if (resultSet.wasNull())
							{
								reader = null; 
							}
					}
					
					if (reader == null)
					{
						objValue = null;
					}
					else
					{
						objValue = getArrayReader(reader, size);
					}
				}
				else if (clazz.equals(Blob.class))
				{
					objValue = resultSet.getBlob(columnIndex.intValue());
					if(resultSet.wasNull())
					{
						objValue = null;
					}
				}
				else if (clazz.equals(Image.class))
				{
					byte[] bytes = readBytes(columnIndex);
					
					if(bytes == null)
					{
						objValue = null;
					}
					else
					{
						objValue = JRImageLoader.loadImage(bytes);
					}					
				}
				else
				{
					objValue = resultSet.getObject(columnIndex.intValue());
				}
			}
			catch (Exception e)
			{
				throw new JRException("Unable to get value for field '" + field.getName() + "' of class '" + clazz.getName() + "'", e);
			}
		}
		
		return objValue;
	}





	/**
	 *
	 */
	private Integer getColumnIndex(String fieldName) throws JRException
	{
		Integer columnIndex = (Integer)columnIndexMap.get(fieldName);
		if (columnIndex == null)
		{
			try
			{
				columnIndex = searchColumnByName(fieldName);
				
				if (columnIndex == null)
				{
					columnIndex = searchColumnByLabel(fieldName);
				}
				
				if (columnIndex == null && fieldName.startsWith(INDEXED_COLUMN_PREFIX))
				{
					columnIndex = Integer.valueOf(fieldName.substring(INDEXED_COLUMN_PREFIX_LENGTH));
					if (
						columnIndex.intValue() <= 0
						|| columnIndex.intValue() > resultSet.getMetaData().getColumnCount()
						)
					{
						throw new JRException("Column index out of range : " + columnIndex);
					}
				}
				
				if (columnIndex == null)
				{
					throw new JRException("Unknown column name : " + fieldName);
				}
			}
			catch (SQLException e)
			{
				throw new JRException("Unable to retrieve result set metadata.", e);
			}

			columnIndexMap.put(fieldName, columnIndex);
		}
		
		return columnIndex;
	}


	protected Integer searchColumnByName(String fieldName) throws SQLException
	{
		Integer columnIndex = null;
		ResultSetMetaData metadata = resultSet.getMetaData();
		for(int i = 1; i <= metadata.getColumnCount(); i++)
		{
			String columnName = metadata.getColumnName(i);
			if (fieldName.equalsIgnoreCase(columnName))
			{
				columnIndex = Integer.valueOf(i);
				break;
			}
		}
		return columnIndex;
	}


	protected Integer searchColumnByLabel(String fieldName) throws SQLException
	{
		Integer columnIndex = null;
		ResultSetMetaData metadata = resultSet.getMetaData();
		for(int i = 1; i <= metadata.getColumnCount(); i++)
		{
			String columnLabel = metadata.getColumnLabel(i);
			if (columnLabel != null && fieldName.equalsIgnoreCase(columnLabel))
			{
				columnIndex = Integer.valueOf(i);
				break;
			}
		}
		return columnIndex;
	}


	protected String clobToString(Clob clob) throws JRException
	{
		try
		{
			int bufSize = 8192;
			char[] buf = new char[bufSize];
			
			Reader reader = new BufferedReader(clob.getCharacterStream(), bufSize);
			StringBuffer str = new StringBuffer((int) clob.length());
			
			for (int read = reader.read(buf); read > 0; read = reader.read(buf))
			{
				str.append(buf, 0, read);
			}

			return str.toString();
		}
		catch (SQLException e)
		{
			throw new JRException("Unable to read clob value", e);
		}
		catch (IOException e)
		{
			throw new JRException("Unable to read clob value", e);
		}
	}

	protected CharArrayReader getArrayReader(Reader reader, long size) throws IOException
	{
		char[] buf = new char[8192];
		CharArrayWriter bufWriter = new CharArrayWriter((size > 0) ? (int) size : 8192);
		
		BufferedReader bufReader = new BufferedReader(reader, 8192);
		for (int read = bufReader.read(buf); read > 0; read = bufReader.read(buf))
		{
			bufWriter.write(buf, 0, read);
		}
		bufWriter.flush();
		
		return new CharArrayReader(bufWriter.toCharArray());
	}

	protected byte[] readBytes(Integer columnIndex) throws SQLException, IOException
	{
		InputStream is = null;
		long size = -1;
		
		int columnType = resultSet.getMetaData().getColumnType(columnIndex.intValue());
		switch (columnType)
		{
			case Types.BLOB:
				Blob blob = resultSet.getBlob(columnIndex.intValue());
				if (!resultSet.wasNull())
				{
					is = blob.getBinaryStream();
					size = blob.length();
				}
				break;
				
			default:
				is = resultSet.getBinaryStream(columnIndex.intValue());
				if (resultSet.wasNull())
				{
					is = null; 
				}
		}
		
		byte[] bytes = null;
		if (is != null)
		{
			bytes = readBytes(is, size);
		}
		
		return bytes;
	}

	protected byte[] readBytes(InputStream is, long size) throws IOException
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream(size > 0 ? (int) size : 1000);
		byte[] bytes = new byte[1000];
		int ln = 0;
		try
		{
			while ((ln = is.read(bytes)) > 0)
			{
				baos.write(bytes, 0, ln);
			}
			baos.flush();
		}
		finally
		{
			try
			{
				baos.close();
			}
			catch(IOException e)
			{
			}
		}
		return baos.toByteArray();
	}
}
