package org.maachang.rimdb.table ;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.sql.Types;
import java.util.Properties;

import org.maachang.rimdb.RimDbException;

/**
 * JDBCからテーブル作成.
 * 
 * @version 2014/07/10
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
public final class JDBCTable extends LoadTable {
    
    /** Fetchサイズ. **/
    private static final int FETCH_SIZE = 100 ;
    
    /** JDBCアクセス条件. **/
    private String driver ;
    private String url ;
    private String user ;
    private String passwd ;
    
    /** SQL文. **/
    private String sql ;
    
    /**
     * コンストラクタ.
     * @param driver 対象のJDBCドライバ名を設定します.
     * @param url 対象のJDBCアクセスURLを設定します.
     * @param user 対象のJDBCアクセスユーザ名を設定します.
     * @param passwd 対象のJDBCアクセスパスワードを設定します.
     * @param sql 対象のデータ読み込みSQL文を設定します.
     */
    public JDBCTable( String driver,String url,String sql ) {
        this( driver,url,null,null,sql ) ;
    }
    
    /**
     * コンストラクタ.
     * @param driver 対象のJDBCドライバ名を設定します.
     * @param url 対象のJDBCアクセスURLを設定します.
     * @param user 対象のJDBCアクセスユーザ名を設定します.
     * @param passwd 対象のJDBCアクセスパスワードを設定します.
     * @param sql 対象のデータ読み込みSQL文を設定します.
     */
    public JDBCTable( String driver,String url,String user,String passwd,String sql ) {
        this.driver = driver ;
        this.url = url ;
        this.user = user ;
        this.passwd = passwd ;
        this.sql = sql ;
    }
    
    /**
     * テーブル作成.
     * @return Table 新しいテーブルオブジェクトが返却されます.
     */
    public final TableImpl create() {
        if( createTable == null ) {
            throw new RimDbException( "テーブル定義が行われていません" ) ;
        }
        int i ;
        Statement st = null ;
        ResultSet rs = null ;
        Connection conn = null ;
        ResultSetMetaData meta = null ;
        try {
            // コネクションを取得.
            conn = driverConnection( driver,url,user,passwd ) ;
            
            // 読み込み専用ステートメント取得.
            st = conn.createStatement(
                ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_READ_ONLY ) ;
            
            // 先読み件数.
            st.setFetchSize( FETCH_SIZE ) ;
            
            // SQL実行.
            rs = st.executeQuery( sql ) ;
            rs.setFetchSize( FETCH_SIZE ) ;
            meta = rs.getMetaData() ;
            
            // データベースカラムデータを取得.
            int cnt = 1 ;
            final int jdbcColumnLen = meta.getColumnCount() ;
            final int[] jdbcColumnTypes = new int[ jdbcColumnLen ] ;
            String[] jdbcColumns = new String[ jdbcColumnLen ] ;
            for( i = 0 ; i < jdbcColumnLen ; i ++ ) {
                jdbcColumns[ i ] = meta.getColumnName( cnt ).toLowerCase() ;
                jdbcColumnTypes[ i ] = getDBType( jdbcColumns[ i ],meta.getColumnType( cnt ) ) ;
                cnt ++ ;
            }
            
            // JDBCカラム順に対するカラム項番リストを取得.
            final int[] columnNoList = TableUtil.headerColumnByNoList( createTable.getColumns(),jdbcColumns ) ;
            jdbcColumns = null ;
            
            // データ取得.
            while( rs.next() ) {
                createTable.next() ;
                for( i = 0 ; i < jdbcColumnLen ; i ++ ) {
                    
                    // 設定されていないカラム名の内容は登録しない.
                    if( columnNoList[ i ] != -1 ) {
                        createTable.set( columnNoList[ i ],getDBTypeByValue( rs,jdbcColumnTypes[ i ],i + 1 ) ) ;
                    }
                }
            }
            
            // DB接続-後始末.
            conn.commit() ;
            meta = null ;
            rs.close() ;
            rs = null ;
            st.close() ;
            st = null ;
            conn.close() ;
            conn = null ;
            
            // テーブル作成.
            return createTable.getTable() ;
            
        } catch( Exception e ) {
            throw new RimDbException( "指定JDBC[driver:" + driver + " url:" + url + " sql:" + sql +
                "]のテーブル作成中にエラーが発生しました",e ) ;
        } finally {
            if( rs != null ) {
                try { rs.close() ; } catch( Exception e ) {}
            }
            if( st != null ) {
                try { st.close() ; } catch( Exception e ) {}
            }
            if( conn != null ) {
                try { conn.close() ; } catch( Exception e ) {}
            }
        }
        
    }
    
    /** 読み込み専用コネクションオブジェクトを取得. **/
    private static final Connection driverConnection( String driver,String url,String user,String passwd )
        throws Exception {
        Class.forName( driver ) ;
        Connection ret ;
        Properties p = new java.util.Properties() ;
        if( user == null || user.length() <= 0 ) {
            p.put("user","") ;
            p.put("password","") ;
            ret = DriverManager.getConnection( url,p ) ;
        }
        else {
            p.put("user",user) ;
            p.put("password",passwd) ;
            ret = DriverManager.getConnection( url,p ) ;
        }
        ret.setReadOnly( false ) ;
        ret.setAutoCommit( false ) ;
        return ret;
    }
    
    /** 対象SQLタイプに対する取得オブジェクトタイプを取得. **/
    private static final int getDBType( final String columnName,final int columnType ) {
        switch( columnType ) {
            case Types.BIT :
            case Types.TINYINT :
                return 1 ;              // boolean(数値).
            case Types.BOOLEAN :
                return 2 ;              // boolean(true/false).
            case Types.SMALLINT :
                return 3 ;              // int.
            case Types.INTEGER :
            case Types.BIGINT :
                return 4 ;              // long.
            case Types.FLOAT :
            case Types.REAL :
                return 5 ;              // float.
            case Types.DOUBLE :
                return 6 ;              // double.
            case Types.NUMERIC :
            case Types.DECIMAL :
                return 7 ;              // bigDecimal.
            case Types.CHAR :
            case Types.VARCHAR :
            case Types.LONGVARCHAR :
            case Types.DATALINK :
                return 8 ;              // string.
            case Types.DATE :
                return 9 ;              // java.sql.Date.
            case Types.TIME :
                return 10 ;              // java.sql.Time.
            case Types.TIMESTAMP :
                return 11 ;              // java.sql.Timestamp.
            default :
                throw new RimDbException( "対象カラム[" + columnName +
                        "]の形式はサポートされていません[" + columnType + "]" ) ;
        }
    }
    
    /** 対象DBTypeから、情報を取得. **/
    private static final Object getDBTypeByValue( final ResultSet rs,final int type,final int no )
        throws Exception {
        if( rs.getObject( no ) == null ) {
            return null ;
        }
        switch( type ) {
            case 1 : return rs.getInt( no ) ;
            case 2 : return rs.getBoolean( no ) ;
            case 3 : return rs.getInt( no ) ;
            case 4 : return rs.getLong( no ) ;
            case 5 : return rs.getFloat( no ) ;
            case 6 : return rs.getDouble( no ) ;
            case 7 : return rs.getBigDecimal( no ) ;
            case 8 : return rs.getString( no ) ;
            case 9 : return rs.getDate( no ) ;
            case 10 : return rs.getTime( no ) ;
            case 11 : return rs.getTimestamp( no ) ;
        }
        return null ;
    }
}
