package com.quantum.sql;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.quantum.model.Bookmark;
import com.quantum.model.Entity;


/**
 * @author BC
 */
public class SQLStandardResultSetResults extends SQLResultSetResults implements Scrollable {

	/**
	 * Some columns -- especially BLOBS and CLOBS can be very wide.  This variable
	 * sets the maximum width of the column.
	 */
    private static final int MAX_COLUMN_WIDTH = 1024 * 2;
	
	private boolean hasMore = false;
	private int start = 1;
	private int numberOfRowsPerPage;
	private int totalNumberOfRows = -1;

	private boolean fullMode = false;
	
	/**
	 * @param bookmark
	 * @param query
	 * @param encoding
	 * @param numberOfRowsPerPage
	 */
	protected SQLStandardResultSetResults(
			Bookmark bookmark, String query, Entity entity,
			int numberOfRowsPerPage) {
		super(query, bookmark, entity);
		this.numberOfRowsPerPage = numberOfRowsPerPage;
	}

	static SQLResultSetResults create(
			ResultSet set, Bookmark bookmark, 
			String query, Entity entity, int numberOfRows) throws SQLException {

		SQLStandardResultSetResults results = new SQLStandardResultSetResults(
				bookmark, query, entity, numberOfRows);
		
		results.parseResultSet(set);
		
		return results;
	}
	
	/**
	 * @param set
	 * @param encoding
	 * @param numberOfRows
	 * @param results
	 * @param columnCount
	 * @throws SQLException
	 */
	protected void parseResultSet(ResultSet set) throws SQLException {
		int rowCount = 1;
		
		ResultSetMetaData metaData = set.getMetaData();
		int columnCount = metaData.getColumnCount();
		List columns = new ArrayList();
		for (int i = 1; i <= columnCount; i++) {
			columns.add(new Column(
					metaData.getColumnName(i), 
					metaData.getColumnTypeName(i),
					metaData.getColumnDisplaySize(i)));
		}
		setColumns((Column[]) columns.toArray(new Column[columns.size()]));
		
		boolean exitEarly = false;
		int firstRow = (this.fullMode) ? 0 : this.start;
		int lastRow = (this.fullMode) ? Integer.MAX_VALUE : this.start + this.numberOfRowsPerPage - 1;
		List rowList = new ArrayList();
		while (set.next()) {
			boolean disable = this.start < 1 || lastRow < 1;
			if (disable || ((rowCount >= firstRow) && (rowCount <= lastRow))) {
				List row = new ArrayList();
				for (int i = 1, length = columns.size(); i <= length; i++) {
					String value = null;
					if (getColumn(i).getSize() < MAX_COLUMN_WIDTH) {
						value = getEncodedString(set, getEncoding(), i);
					} else {
						try {
							if ("".equals(getEncoding())) { //$NON-NLS-1$
								value = getStringFromCharacterSteam(set, i);
							} else {
								value = getEncodedStringFromBinaryStream(set, getEncoding(), i);
							}
						} catch (IOException e) {
							value = set.getString(i);
						} catch (RuntimeException e) {
							// hack for mysql which doesn't implement
							// character streams
							value = set.getString(i);
						}
					}
					if (value == null && !set.wasNull()) {
						value = set.getString(i);
					}
					row.add(value == null || set.wasNull() ? "<NULL>" : value); //$NON-NLS-1$
				}
				rowList.add(new Row(row));
			}
			rowCount++;
			if (!disable && (rowCount > lastRow)) {
				exitEarly = true;
				break;
			}
		}
		if (exitEarly) {
			this.hasMore = set.next();
		} else {
			this.totalNumberOfRows = Math.max(0, rowCount-1);
			this.hasMore = false;
		}
		setRows((Row[]) rowList.toArray(new Row[rowList.size()]));
	}


	/**
	 * @param set
	 * @param encoding
	 * @param columnNumber
	 * @throws SQLException
	 * @throws IOException
	 * @throws UnsupportedEncodingException
	 */
	private String getEncodedStringFromBinaryStream(ResultSet set, String encoding, int columnNumber) 
			throws SQLException, IOException, UnsupportedEncodingException {
		InputStream binaryStream = set.getBinaryStream(columnNumber);
		if (binaryStream != null) {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			try {
				for (int c = binaryStream.read(), count = 0; 
					c >= 0 && count <= MAX_COLUMN_WIDTH; 
					c = binaryStream.read(), count++) {
					baos.write(c);
				}
			} finally {
				binaryStream.close();
			}
			return new String(baos.toByteArray(), encoding);
		} else {
			return null;
		}
	}


	/**
	 * @param set
	 * @param columnNumber
	 * @throws SQLException
	 * @throws IOException
	 */
	private String getStringFromCharacterSteam(ResultSet set, int columnNumber) 
			throws SQLException, IOException {
		Reader reader = set.getCharacterStream(columnNumber);
		if (reader != null) {
			StringBuffer buffer = new StringBuffer();
			int retVal = reader.read();
			int count = 0;
			while (retVal >= 0) {
				buffer.append((char) retVal);
				retVal = reader.read();
				count++;
				if (count > MAX_COLUMN_WIDTH) {
					buffer.append("...>>>"); //$NON-NLS-1$
					break;
				}
			}
			reader.close();
			return buffer.toString();
		} else {
			return null;
		}
	}


	/**
	 * @param set
	 * @param encoding
	 * @param index
	 * @return
	 * @throws SQLException
	 */
	private String getEncodedString(ResultSet set, String encoding, int index) 
			throws SQLException {
		try {
			return encoding == null || encoding.trim().length() == 0 
				? set.getString(index) 
				: new String(set.getBytes(index), encoding);
		} catch (UnsupportedEncodingException e) {
			return set.getString(index);
		}
	}

	public boolean hasMore() {
		return this.hasMore;
	}
	
	public int getTotalNumberOfRows() {
		return this.totalNumberOfRows;
	}
	public void nextPage(Connection connection) throws SQLException {
		if (hasNextPage()) {
			this.start += this.numberOfRowsPerPage;
			refresh(connection);
		}
	}

	/* (non-Javadoc)
	 * @see com.quantum.sql.Scrollable#previousPage(java.sql.Connection)
	 */
	public void previousPage(Connection connection) throws SQLException {
		if (hasPreviousPage()) {
			this.start = Math.max(1, this.start - this.numberOfRowsPerPage);
			refresh(connection);
		}
	}

	/* (non-Javadoc)
	 * @see com.quantum.sql.Scrollable#hasNextPage()
	 */
	public boolean hasNextPage() {
		return this.hasMore;
	}

	/* (non-Javadoc)
	 * @see com.quantum.sql.Scrollable#hasPreviousPage()
	 */
	public boolean hasPreviousPage() {
		return this.start > 1;
	}

	public void setFullMode(boolean fullMode) {
		this.fullMode = fullMode;
	}
	public boolean isFullMode() {
		return this.fullMode;
	}

	/* (non-Javadoc)
	 * @see com.quantum.sql.Scrollable#getStart()
	 */
	public int getStart() {
		return getRowCount() == 0 ? 0 : (this.fullMode ? 1 : this.start);
	}

	/* (non-Javadoc)
	 * @see com.quantum.sql.Scrollable#getEnd()
	 */
	public int getEnd() {
		return this.fullMode 
			? getRowCount() 
			: this.start + getRowCount() - 1;
	}

	/* (non-Javadoc)
	 * @see com.quantum.sql.Scrollable#getLast()
	 */
	public int getLast() {
		return this.totalNumberOfRows;
	}
	
	
	public void setFilterSort(FilterSort filterSort) {
		super.setFilterSort(filterSort);
		this.start = 1;
	}
}