package org.postgresforest.vm.jdbc;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

import org.postgresforest.Driver;
import org.postgresforest.core.Field;
import org.postgresforest.core.ParameterList;
import org.postgresforest.core.Query;
import org.postgresforest.core.ResultCursor;
import org.postgresforest.jdbc3.AbstractJdbc3Statement;
import org.postgresforest.util.GT;
import org.postgresforest.util.PSQLException;
import org.postgresforest.vm.ColumnInfo;
import org.postgresforest.vm.LogUtil;
import org.postgresforest.vm.NullKey;
import org.postgresforest.vm.OrderInfo;
import org.postgresforest.vm.Parser;
import org.postgresforest.vm.ReWriter;
import org.postgresforest.vm.SelectColumnInfo;
import org.postgresforest.vm.core.QueryExecutorImpl;
import org.postgresforest.vm.core.SimpleQuery;
import org.postgresforest.vm.err.ForestSQLState;
import org.postgresforest.vm.gsc.GscData;


public class ForestStatement extends AbstractJdbc3Statement implements Statement{

	/** ログ出力 @since 2.1 */
	protected LogUtil m_logUtil;
	protected QueryExecutorImpl m_queryExecutor;

	/** グローバルシステムカタログ情報 */
	protected GscData m_gsc;
	
	public ForestStatement(ForestConnection c, int rsType, int rsConcurrency, int rsHoldability) throws SQLException {
		super(c, rsType, rsConcurrency, rsHoldability);
		m_queryExecutor = (QueryExecutorImpl)connection.getQueryExecutor();
		m_logUtil = m_queryExecutor.getLogUtil();
		m_gsc = m_queryExecutor.getGsc();
		
	}

	public ForestStatement(ForestConnection connection, String sql, boolean isCallable, int rsType, int rsConcurrency, int rsHoldability) throws SQLException {
		super(connection, sql, isCallable, rsType, rsConcurrency, rsHoldability);
		//パーサー時エラーを検出
		SQLException ex = ((SimpleQuery)preparedQuery).getParserErr();
		if(ex!= null)
			throw ex;
		
		m_queryExecutor = (QueryExecutorImpl)connection.getQueryExecutor();
		m_logUtil = m_queryExecutor.getLogUtil();
		m_gsc = m_queryExecutor.getGsc();
	}

	public ResultSet createResultSet(Query originalQuery, Field[] fields, Vector tuples, ResultCursor cursor) throws SQLException {
		ForestResultSet newResult = new ForestResultSet(originalQuery, this, fields, tuples, cursor,
                getMaxRows(), getMaxFieldSize(),
                getResultSetType(), getResultSetConcurrency(), getResultSetHoldability());
        newResult.setFetchSize(getFetchSize());
        newResult.setFetchDirection(getFetchDirection());
        return newResult;
	}

	protected void execute(Query queryToExecute, ParameterList queryParameters, int flags) throws SQLException {
		
		//Ver3.1 rollback指示があるか
		chkRollBack();


		//Ver3.1 オンラインメンテ(AutoCommitがTrueの場合)
		if ( connection.getAutoCommit()==true && !m_gsc.isTableAvailable() )
		{
        	m_gsc.waitTableAvailable();
		
		}

		
		//Queryにタイムアウトを設定
		SimpleQuery simpleQuery = (SimpleQuery)queryToExecute;
		simpleQuery.setTimeout(timeout);
		
		
		
		try {
			//SQL実行処理
			super.execute(queryToExecute, queryParameters, flags);
		} catch (SQLException e1) {

			throw e1;
		}finally{
			//Ver3.1 rollback指示があるか
			chkRollBack();
		}
		

		
		Parser parser = simpleQuery.getParser();
		ForestResultSet rs = (ForestResultSet)result.getResultSet();
		
		//Ver2.0 集約関数(GROUP BY)、ORDER BY, LIMIT　の処理 
		//パーティションがひとつか？
		if(simpleQuery.getQueryType() == ReWriter.TYPE_PARTITION && simpleQuery.getQueryNum() > 1){

			try {//フィールドの型を取得するためにDBにアクセスするのでMetaDataアクセスをセット
				m_queryExecutor.addMetaDataAccess();

			//集約処理対象の関数があるか、GRUOP　BY　の指定があるか？
			if(parser.hasGroupBy()     || 
					parser.hasExecFunction()   ){


				ArrayList grpRowList = new ArrayList();
				Vector rows = rs.getRows();
				//GROUP BYが指定されているか？
				if(parser.hasGroupBy()){

					ArrayList groupList = parser.getGroupList();
					
					//GROUP BY に指定された項目をKEYにしてMAP作成
					TreeMap sortMap = sortRows(rs, groupList);

					//GROUP BYで集約したレコードのリストを作成
					getGroupRows(sortMap, groupList, 0, grpRowList);


				}else{
					//GROUP BY が指定されていなければ、全てをひとつのグループにする
					grpRowList.add(rows.clone());
				}
				
				//一旦結果のタプルをクリアー
				rows.clear();
				
				ArrayList selectList = parser.getSelectList();
				
				//集計処理
				for (int i = 0; i < grpRowList.size(); i++) {

					//集約レコードを一時RecordSetに格納する
					Vector groupRows = new Vector( (Collection)grpRowList.get(i));
					ForestResultSet tmpRes = rs.Copy( );
					tmpRes.setRows(groupRows);

					//集計結果をタプルにセット							
					rows.add(groupRows.get(0));
					rs._last();

					tmpRes.next();//最初の一行はすでにセットされているので読み飛ばし
					//残りの行の演算を行う
					while(tmpRes.next()){
						//関数情報に従い、演算
						for (int j = 0; j < selectList.size(); j++) {

							SelectColumnInfo selectColumnInfo = (SelectColumnInfo) selectList.get(j);
							int columnIndex = selectColumnInfo.getNumber();

							//比較用オブジェクト
							Comparable tmpValue = null;
							Comparable srcValue = null;
							//演算用
							long tmpLongValue;
							long srcLongValue;
							double tmpDoubleValue;
							double srcDoubleValue;

							//結果用オブジェクト
							Object distValue = null;
							
							
							switch (selectColumnInfo.getFunctionType()) {
								case SelectColumnInfo.FUNCTION_COUNT :
									//加算
									tmpLongValue = tmpRes.getLong(columnIndex);
									srcLongValue = rs.getLong(columnIndex);
									distValue = new Long(tmpLongValue + srcLongValue);
									break;

								case SelectColumnInfo.FUNCTION_MAX :
									//最大値
									tmpValue = (Comparable)tmpRes.getObject(columnIndex);
								    srcValue = (Comparable)rs.getObject(columnIndex);
									if(tmpValue != null && srcValue != null){
										if(tmpValue.compareTo(srcValue) > 0){
											distValue = tmpValue;
										}else{
											distValue = srcValue;
										}
									}
									else if(tmpValue != null){
										distValue = tmpValue;

									}
									else if(srcValue != null){
										distValue = srcValue;
									}
									break;

								case SelectColumnInfo.FUNCTION_MIN :
									//最小値
									tmpValue = (Comparable)tmpRes.getObject(columnIndex);
									srcValue = (Comparable)rs.getObject(columnIndex);
									if(tmpValue != null && srcValue != null){
										if(tmpValue.compareTo(srcValue) < 0){
											distValue = tmpValue;
										}else{
											distValue = srcValue;
										}
									}
									else if(tmpValue != null){
										distValue = tmpValue;
									}
									else if(srcValue != null){
										distValue = srcValue;
									}
									break;

								case SelectColumnInfo.FUNCTION_SUM :
									//加算
									//各型に合わせて演算を行う
								    ResultSetMetaData rmeta= rs.getMetaData();
									switch (rmeta.getColumnType(columnIndex)) {
											
										case Types.SMALLINT:
										case Types.INTEGER:
										case Types.BIGINT:
											tmpLongValue = tmpRes.getLong(columnIndex);
											srcLongValue = rs.getLong(columnIndex);
											distValue = new Long(tmpLongValue + srcLongValue);
											break;

										case Types.NUMERIC:
											BigDecimal tmpBigDecimal = (BigDecimal)tmpRes.getObject(columnIndex);
											BigDecimal srcBigDecimal = (BigDecimal)rs.getObject(columnIndex);
											distValue = srcBigDecimal.add(tmpBigDecimal);
											break;

										case Types.DOUBLE:
										case Types.REAL:
										case Types.FLOAT:
											tmpDoubleValue = tmpRes.getDouble(columnIndex);
											srcDoubleValue = rs.getDouble(columnIndex);
											distValue = new Double(tmpDoubleValue + srcDoubleValue);


										default :
											break;
									}
									break;

								default :
									break;
							}
							

							//結果をタプルにセット
							if(distValue != null){
								rs.setObject(columnIndex, distValue );
							}
						}
					}
				}

				
				
				//Rusultセットの現在行を初期化
				rs._beforeFirst();
					
			}
			

			//ORDER BYが指定されているか?
			if(parser.hasOrderBy() ){

				ArrayList orderList = parser.getOrderList();

				TreeMap sortMap = sortRows(rs, orderList);
				
				//ソート結果の取り出し
				Vector sortRows = new Vector(getSortRows(sortMap,orderList,0));
				//ソート結果のセット
				rs.setRows(sortRows);
				//Rusultセットの現在行を初期化
				rs._beforeFirst();

			}

			//3.2
			}catch (SQLException e) {
				//SQLExceptionはそのままthrow
				throw e;

			}catch (Exception e) {
				//SQLException以外のExceptionは内部エラーとしてthrowする
				//その他のExceptionはSQLExceptionでthrow
				throw new PSQLException(GT.tr("excute internal error.\n{0}",e), ForestSQLState.INTERNAL_ERROR,e);
			
			} finally {
				m_queryExecutor.removeMetaDataAccess();
			}
			
			//LIMITが指定されているか？
			if(parser.hasLimit()){

				//LIMITの行数切り出し処理
				//現在のタプル取り出し
				Vector rows = rs.getRows();
				//LIMITで指定された行数を切り出し
				int rowCount = parser.getLimitCount();
				if(rows.size() < rowCount){
					rowCount = rows.size();
				}
				rows = new Vector(rows.subList(0,rowCount));
				//Resultにセット
				rs.setRows(rows);
			}
		}

		
	}

	private TreeMap sortRows(ForestResultSet rs, ArrayList orderList) throws SQLException {

		TreeMap sortMap = new TreeMap();
		Vector rows = rs.getRows();
		while(rs.next()){
		    if (Driver.logDebug)
		        m_logUtil.debug("Row:" + rs.getRow());
			sortRows(rs, sortMap, orderList, 0, rows);
		}
		return sortMap;
	}


	
   /**
	 * 結果ソート処理.
	 *  
	 * @param sortMap 結果格納用
	 * @param grpList	グループリスト
	 * @param index グループ階層INDEX
	 * @param rows	値（タプル値）
	 * @throws SQLException
	 * @since 2.0
	 */
	protected  void sortRows(ResultSet rs, TreeMap sortMap, ArrayList grpList, int index, Vector rows)
		throws SQLException {

		
		ColumnInfo columnInfo = (ColumnInfo)grpList.get(index);
		Object objKey = rs.getObject(columnInfo.getNumber());
		if(objKey == null || (objKey instanceof Comparable) == false ){
			//値がNULLの場合、またはComparableでないばあい代理のオブジェクトをキーにする
			objKey = new NullKey();
		}

		if (Driver.logDebug)
		    m_logUtil.debug("TREE:[" +index+ "]"+ objKey.toString());

		
		if(grpList.size() == index + 1 ){
			//この階層の終わり
			
			//値を取り出し
			Object value = rows.get( rs.getRow()-1);
			if(sortMap.containsKey(objKey)){
				//すでにソート結果にあるキーならば、値のリストに値を追加
				ArrayList values = (ArrayList)sortMap.get(objKey);
				values.add(value);
		
			}else{
				//キーが登録されていなければ、treeに追加
				ArrayList values = new ArrayList();
				values.add(value);
				sortMap.put(objKey,values);
			}

		}else{
			//下の階層があれば下の階層のソートを行う
			TreeMap subTreeMap = null;		
			if(sortMap.containsKey(objKey)){
				subTreeMap = (TreeMap)sortMap.get(objKey);
				sortRows(rs, subTreeMap, grpList, ++index, rows);
			}else{
				subTreeMap = new TreeMap();
				sortRows(rs, subTreeMap, grpList, index + 1, rows);
				sortMap.put(objKey,subTreeMap);
			}

		}

	}

	/**
	 * ソート結果をリストで取得.
	 * 
	 * @param sortMap ソート結果
	 * @param cols グループリスト
	 * @param index　グループ階層INDEX
	 * @return　ソート結果のリスト
	 * @throws SQLException
	 * @since 2.0
	 */	
	protected  Collection getSortRows(TreeMap sortMap, ArrayList cols, int index)
		throws SQLException {

		OrderInfo orderInfo = (OrderInfo)cols.get(index);

		ArrayList sortRows = new ArrayList();

		ArrayList keys = new ArrayList(sortMap.keySet());
		if(orderInfo.getSort() == OrderInfo.DESC){
			//DESC が設定されていたら、
			//順序を逆転
			Collections.reverse(keys);
		}
		
		for (int i = 0; i < keys.size(); i++) {
			Object key = keys.get(i);
			if (Driver.logDebug)
			    m_logUtil.debug(key.toString());
			if(cols.size() == index + 1 ){
				//ツリー末端
				sortRows.addAll( (Collection)sortMap.get(key) );
			}else{
				//ツリー途中
				TreeMap subSortMap = (TreeMap)sortMap.get(key);
				sortRows.addAll( getSortRows(subSortMap, cols, index + 1) );
		
			}
		}
		return sortRows;

	}


	/**
	 * GROUP BYで集約したレコードのリストを作成
	 * @param sortMap ソート結果
	 * @param cols グループリスト
	 * @param index グループ階層INDEX 
	 * @param sortRows 結果格納用
	 * @throws SQLException
	 * @since 2.0
	 */
	protected  void getGroupRows(Map sortMap, ArrayList cols, int index, Collection sortRows)
		throws SQLException {

		OrderInfo orderInfo = (OrderInfo)cols.get(index);

		ArrayList keys = new ArrayList(sortMap.keySet());
		for (int i = 0; i < keys.size(); i++) {
			Object key = keys.get(i);
			if (Driver.logDebug)
			    m_logUtil.debug(key.toString());
			if(cols.size() == index + 1 ){
				//ツリー末端
				sortRows.add( (Collection)sortMap.get(key) );
			}else{
				//ツリー途中
				Map subMap = (Map)sortMap.get(key);
				getGroupRows(subMap, cols, index + 1, sortRows) ;
			}
		}

	}

    /**
     * @throws SQLException
     * @since 3.1
     */
    protected void chkRollBack() throws SQLException {
    	//Ver3.1 rollback対象のエラー処理
		if( m_queryExecutor.isRollback() ){
			
			m_queryExecutor.clearRollback();
		    //ロールバック処理
		    try {
                connection.rollback();
            } catch (SQLException e) {}
            
		    throw m_queryExecutor.getRollbackException();
		}
    }


}
