package org.maachang.dao.dbms.ctbl;

import java.util.HashSet;

import org.maachang.conf.Config;
import org.maachang.dao.dbms.kind.SupportKind;
import org.maachang.util.StringUtil;

/**
 * テーブル定義(HSQL).
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangDao 1.00
 */
public class TableHSql implements Table {
    
    /**
     * アダプタ名を取得.
     * <BR><BR>
     * アダプタ名を取得します.
     * <BR>
     * @return String アダプタ名が返されます.
     */
    public String getAdapter() {
        return "hsqldb" ;
    }
    
    /**
     * 指定パラメータから、CreateTable条件を生成.
     * <BR><BR>
     * 指定パラメータから、CreateTable条件を生成します.
     * <BR>
     * @param params 対象のパラメータを設定します.
     * @return String 生成されたCreateTableが返されます.
     */
    public String createTable( Config params ) {
        StringBuilder buf = new StringBuilder() ;
        Object[] sections = params.getSections() ;
        if( sections != null && sections.length > 0 ) {
            int len = sections.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( sections[ i ] != null ) {
                    createOneSection( buf,( String )sections[ i ],params ) ;
                }
            }
            return buf.toString() ;
        }
        return null ;
    }
    
    /**
     * boolean型を取得.
     * <BR><BR>
     * boolean型を取得します.
     * <BR>
     * @return String boolean型の文字列が返されます.
     */
    public String typeByBoolean() {
        return "BOOLEAN" ;
    }
    
    /**
     * intの条件を取得.
     * <BR><BR>
     * intの条件を取得します.
     * <BR>
     * @return String int型の文字列が返されます.
     */
    public String typeByInt() {
        return "INTEGER" ;
    }
    
    /**
     * longの条件を取得.
     * <BR><BR>
     * longの条件を取得します.
     * <BR>
     * @return String long型の文字列が返されます.
     */
    public String typeByLong() {
        return "BIGINT" ;
    }
    
    /**
     * floatの条件を取得.
     * <BR><BR>
     * floatの条件を取得します.
     * <BR>
     * @return String float型の文字列が返されます.
     */
    public String typeByFloat() {
        return "REAL" ;
    }
    
    /**
     * doubleの条件を取得.
     * <BR><BR>
     * doubleの条件を取得します.
     * <BR>
     * @return String double型の文字列が返されます.
     */
    public String typeByDouble() {
        return "DOUBLE PRECISION" ;
    }
    
    /**
     * byte配列の条件を取得.
     * <BR><BR>
     * byte配列の条件を取得します.
     * <BR>
     * @return String byte配列型の文字列が返されます.
     */
    public String typeByBytes() {
        return "BLOB" ;
    }
    
    /**
     * char条件を取得.
     * <BR><BR>
     * char条件を取得します.
     * <BR>
     * @return String char型の文字列が返されます.
     */
    public String typeByChar() {
        return "VARCHAR(255)" ;
    }
    
    /**
     * text条件を取得.
     * <BR><BR>
     * text条件を取得します.
     * <BR>
     * @return String text型の文字列が返されます.
     */
    public String typeByText() {
        return "LONG VARCHAR" ;
    }
    
    /**
     * date条件を取得.
     * <BR><BR>
     * date条件を取得します.
     * <BR>
     * @return String date型の文字列が返されます.
     */
    public String typeByDate() {
        return "DATE" ;
    }
    
    /**
     * time条件を取得.
     * <BR><BR>
     * time条件を取得します.
     * <BR>
     * @return String time型の文字列が返されます.
     */
    public String typeByTime() {
        return "TIME" ;
    }
    
    /**
     * timestamp条件を取得.
     * <BR><BR>
     * timestamp条件を取得します.
     * <BR>
     * @return String timestamp型の文字列が返されます.
     */
    public String typeByTimestamp() {
        return "TIMESTAMP" ;
    }
    
    /**
     * プライマリーキー条件を取得.
     * <BR><BR>
     * プライマリーキー条件を取得します.
     * <BR>
     * @return String プライマリーキー条件の文字列が返されます.
     */
    public String primaryKeyName() {
        return "CONSTRAINT [KEY_NAME] PRIMARY KEY" ;
    }
    
    /*
     * インデックス条件を取得.
     * <BR><BR>
     * インデックス条件を取得します.
     * <BR>
     * @return String インデックス条件の文字列が返されます.
     */
    public String indexName() {
        return null ;
    }
    
    /**
     * not_null条件を取得.
     * <BR><BR>
     * not_null条件を取得します.
     * <BR>
     * @return String not_null条件の文字列が返されます.
     */
    public String notNullName() {
        return "NOT NULL" ;
    }
    
    /**
     * unique条件を取得.
     * <BR><BR>
     * unique条件を取得します.
     * <BR>
     * @return String unique条件の文字列が返されます.
     */
    public String uniqueName() {
        return null ;
    }
    
    /**
     * 対象１セクションからテーブルを構成.
     */
    private void createOneSection( StringBuilder buf,String section,Config params ) {
        if( section == null ) {
            return ;
        }
        Object[] keys = params.getKeys( section ) ;
        if( keys != null && keys.length > 0 ) {
            int len = keys.length ;
            HashSet<String> primaryKey = new HashSet<String>() ;
            buf.append( dropTable( section ) ) ;
            buf.append( "create table " ).append( section ).append( "(" ) ;
            // id.
            buf.append( "id BIGINT NOT NULL" ) ;
            boolean next = true ;
            // 定義されている各カラム条件.
            for( int i = 0 ; i < len ; i ++ ) {
                if( next == true ) {
                    buf.append( "," ) ;
                    next = false ;
                }
                String key = ( String )keys[ i ] ;
                String type = null ;
                String not_null = null ;
                String unique = null ;
                String[] one = params.getAll( section,key ) ;
                int lenJ = one.length ;
                int[] mode = new int[ 1 ] ;
                
                if( "id".equals( key.toLowerCase() ) ) {
                    continue ;
                }
                // テーブルタイプが定義されている場合.
                if( TableUtil.isType( key ) == true ) {
                    continue ;
                }
                // カラム定義.
                else {
                    for( int j = 0 ; j < lenJ ; j ++ ) {
                        String tmp = null ;
                        tmp = TableUtil.getColumnName( this,one[ j ] ) ;
                        if( tmp != null ) {
                            type = tmp ;
                            continue ;
                        }
                        tmp = TableUtil.getColumnType( mode,this,one[ j ] ) ;
                        if( tmp != null ) {
                            switch( mode[ 0 ] ) {
                                case 1 : not_null = tmp ; break ;
                                case 2 : unique = tmp ; break ;
                            }
                            continue ;
                        }
                        if( TableUtil.isIndex( one[ j ] ) == true ) {
                            // hsqlでは、indexは、プライマリーキーとする.
                            primaryKey.add( key.toLowerCase() ) ;
                            not_null = notNullName() ;
                        }
                        if( TableUtil.isKey( one[ j ] ) == true ) {
                            primaryKey.add( key.toLowerCase() ) ;
                            not_null = notNullName() ;
                        }
                    }
                    if( type == null ) {
                        continue ;
                    }
                    buf.append( key.toLowerCase() ).
                        append( " " ).
                        append( type ) ;
                    if( unique != null ) {
                        buf.append( " " ).append( unique ) ;
                    }
                    if( not_null != null ) {
                        buf.append( " " ).append( not_null ) ;
                    }
                    next = true ;
                }
            }
            
            if( buf.toString().endsWith( "," ) == false ) {
                buf.append( "," ) ;
            }
            if( primaryKey.size() > 0 ) {
                buf.append( StringUtil.changeString( primaryKeyName(),"[KEY_NAME]",section.toLowerCase()+"_pk" ) ).append( "(" ) ;
                buf.append( "id" ) ;
                Object[] vals = primaryKey.toArray() ;
                int lenI = vals.length ;
                for( int i = 0 ; i < lenI ; i ++ ) {
                    buf.append( "," ).append( vals[ i ] ) ;
                }
                buf.append( ")" ) ;
            }
            else {
                buf.append( StringUtil.changeString( primaryKeyName(),"[KEY_NAME]",section.toLowerCase()+"_pk" ) ) ;
                buf.append( "(id)" ) ;
            }
            buf.append( ") ; \n" ) ;
            // hsqlでは、シーケンスIDを設定.
            buf.append( "create sequence " ).append( section.toLowerCase() ).
                append( SupportKind.SEQ_NAME ).append( "; \n" ) ;
        }
    }
    
    /**
     * 指定名のテーブル作成SQL文を生成.
     * <BR><BR>
     * 指定名のテーブル作成SQL文を生成します.
     * <BR>
     * @param name 対象のテーブル名を設定します.
     * @param config 対象のコンフィグ情報を設定します.
     * @return String 対象のSQL文が返されます.
     */
    public String createTable( String name,Config config ) {
        StringBuilder buf = new StringBuilder() ;
        createOneSection( buf,name,config ) ;
        return buf.toString() ;
    }
    
    /**
     * 指定名のテーブル削除SQL文を生成.
     * <BR><BR>
     * 指定名のテーブル削除SQL文を生成します.
     * <BR>
     * @param name 対象のテーブル名を設定します.
     * @return String 削除用SQL文が返されます.
     */
    public String dropTable( String name ) {
        StringBuilder buf = new StringBuilder() ;
        buf.append( "drop table " ).append( name ).append( "; \n" ) ;
        return buf.toString() ;
    }
    
    /**
     * 指定テーブルにカラム追加.
     * <BR><BR>
     * 指定テーブルにカラム追加を行います.
     * <BR>
     * @param table 対象のテーブル名を設定します.
     * @param column 対象のカラム名を設定します.
     * @param config 対象のコンフィグ情報を設定します.
     * @return String 対象のSQL文が返されます.
     */
    public String addColumn( String table,String column,Config config ) {
        if( "id".equals( column.toLowerCase() ) == true || TableUtil.isType( column ) == true ) {
            return "" ;
        }
        StringBuilder buf = new StringBuilder() ;
        buf.append( "alter table " ).append( table ).append( " add " ) ;
            
        String type = null ;
        String not_null = null ;
        String unique = null ;
        String[] one = config.getAll( table,column ) ;
        int lenJ = one.length ;
        int[] mode = new int[ 1 ] ;
        
        for( int j = 0 ; j < lenJ ; j ++ ) {
            String tmp = null ;
            tmp = TableUtil.getColumnName( this,one[ j ] ) ;
            if( tmp != null ) {
                type = tmp ;
                continue ;
            }
            tmp = TableUtil.getColumnType( mode,this,one[ j ] ) ;
            if( tmp != null ) {
                switch( mode[ 0 ] ) {
                    case 1 : not_null = tmp ; break ;
                    case 2 : unique = tmp ; break ;
                }
                continue ;
            }
            if( TableUtil.isIndex( one[ j ] ) || TableUtil.isKey( one[ j ] ) ) {
                not_null = notNullName() ;
            }
        }
        if( type == null ) {
            return "" ;
        }
        buf.append( column.toLowerCase() ).
            append( " " ).
            append( type ) ;
        if( unique != null ) {
            buf.append( " " ).append( unique ) ;
        }
        if( not_null != null ) {
            buf.append( " " ).append( not_null ) ;
        }
        buf.append( "; \n" ) ;
        return buf.toString() ;
    }
    
    /**
     * 指定テーブルにカラム削除.
     * <BR><BR>
     * 指定テーブルから指定カラムを削除します.
     * <BR>
     * @param table 対象のテーブル名を設定します.
     * @param column 対象のカラム名を設定します.
     * @return String 対象のSQL文が返されます.
     */
    public String dropColumn( String table,String column ) {
        if( "id".equals( column.toLowerCase() ) == true || TableUtil.isType( column ) == true ) {
            return "" ;
        }
        StringBuilder buf = new StringBuilder() ;
        buf.append( "alter table " ).append( table ).
            append( " drop " ).append( column ).append( "; \n" ) ;
        return buf.toString() ;
    }
    
    /**
     * 指定テーブルのプライマリキーを追加.
     * <BR><BR>
     * 指定テーブルのプライマリキーを追加します.
     * <BR>
     * @param table 対象のテーブル名を設定します.
     * @param config 対象のコンフィグ情報を設定します.
     * @return String 対象のSQL文が返されます.
     */
    public String addPrimaryKey( String table,Config config ) {
        Object[] keys = config.getKeys( table ) ;
        if( keys != null && keys.length > 0 ) {
            StringBuilder buf = new StringBuilder() ;
            buf.append( "alter table " ).append( table ).append( " add " ) ;
            int len = keys.length ;
            HashSet<String> primaryKey = new HashSet<String>() ;
            for( int i = 0 ; i < len ; i ++ ) {
                String key = ( String )keys[ i ] ;
                if( "id".equals( key.toLowerCase() ) ) {
                    continue ;
                }
                if( TableUtil.isType( key ) == true ) {
                    continue ;
                }
                else {
                    String[] one = config.getAll( table,key ) ;
                    int lenJ = one.length ;
                    for( int j = 0 ; j < lenJ ; j ++ ) {
                        if( TableUtil.isIndex( one[ j ] ) == true || TableUtil.isKey( one[ j ] ) == true ) {
                            primaryKey.add( key.toLowerCase() ) ;
                        }
                    }
                }
            }
            if( primaryKey.size() > 0 ) {
                buf.append( StringUtil.changeString( primaryKeyName(),"[KEY_NAME]",table.toLowerCase()+"_pk" ) ).append( "(" ) ;
                buf.append( "id" ) ;
                Object[] vals = primaryKey.toArray() ;
                int lenI = vals.length ;
                for( int i = 0 ; i < lenI ; i ++ ) {
                    buf.append( "," ).append( vals[ i ] ) ;
                }
                buf.append( ")" ) ;
            }
            else {
                buf.append( StringUtil.changeString( primaryKeyName(),"[KEY_NAME]",table.toLowerCase()+"_pk" ) ) ;
                buf.append( "(id)" ) ;
            }
            buf.append( ") ; \n" ) ;
            return buf.toString() ;
        }
        return "" ;
    }
    
    /**
     * 指定テーブルのプライマリキーを削除.
     * <BR><BR>
     * 指定テーブルのプライマリキーを削除します.
     * <BR>
     * @param table 対象のテーブル名を設定します.
     * @return String 対象のSQL文が返されます.
     */
    public String dropPrimaryKey( String table ) {
        return new StringBuilder().append( "alter table " ).append( table ).append( " drop constraint " ).
            append( table ).append( "_pk" ).append( "; \n" ).toString() ;
    }
    
    /**
     * 指定テーブルのシーケンスIDを削除.
     * <BR><BR>
     * 指定テーブルのシーケンスIDを削除します.
     * <BR>
     * @param table 対象のテーブル名を設定します.
     * @return String 対象のSQL文が返されます.
     */
    public String dropSequence( String table ) {
        StringBuilder buf = new StringBuilder() ;
        buf.append( "drop sequence " ).append( table ).
            append( SupportKind.SEQ_NAME ).append( "; \n" ) ;
        return buf.toString() ;
    }
    
    /**
     * BOOLEAN型[true]情報.
     * <BR><BR>
     * BOOLEAN型の[true]情報が返されます.
     * <BR>
     * @return String Boolean型の[true]条件が返されます.
     */
    public String getBooleanByTrue() {
        return "true" ;
    }
    
    /**
     * BOOLEAN型[false]情報.
     * <BR><BR>
     * BOOLEAN型の[false]情報が返されます.
     * <BR>
     * @return String Boolean型の[false]条件が返されます.
     */
    public String getBooleanByFalse() {
        return "false" ;
    }
    
    /**
     * 文字サイズに対するUTF8計算.
     * <BR><BR>
     * 文字サイズに対するUTF8計算処理を行います.
     * @param size 指定サイズを設定します.
     * @return int 計算された結果が返されます.
     */
    public int getUTF8CharSize( int size ) {
        return size * 3 ;
    }
    
    /**
     * SQLの最後に「；」セミコロンをつけるか返す.
     * <BR><BR>
     * SQLの最後に「；」セミコロンを付けるかを返します.<br>
     * これは、JDBC経由でアクセスしたとき、SQL文の終わりに「；」をつけると
     * Syntaxエラーになるものが存在するためです.
     * <br>
     * @return String SQLの最後に付ける場合は、「；」そのものが返されます.
     */
    public String getSemicolon() {
        return ";" ;
    }
    
    /**
     * Insert時にIDを直接設定できるかチェック.
     * <BR><BR>
     * Insert時にIDを直接設定できるかチェックします.
     * <BR>
     * @return boolean [true]の場合、IDを付加できます.
     */
    public boolean isInsertId() {
        return true ;
    }
    
    /**
     * 指定型の文字列条件を正しい値に変換.
     * <BR><BR>
     * 指定型の文字列条件を正しい値に変換します.
     * <BR>
     * @param type 指定型を設定します.
     * @param value 対象文字列を設定します.
     * @return String 文字列が返されます.
     */
    public String convertTypeToData( String type,String value ) {
        return value ;
    }
}
