package org.maachang.rimdb.table ;

import java.lang.reflect.Array;
import java.util.Arrays;

import org.maachang.rimdb.ColumnType;
import org.maachang.rimdb.RimDbException;
import org.maachang.rimdb.index.Index;
import org.maachang.rimdb.index.IndexUtil;
import org.maachang.rimdb.index.bool.CreateBoolIndex;
import org.maachang.rimdb.index.comparable.CreateComparableIndex;
import org.maachang.rimdb.index.number32.CreateNumber32Index;
import org.maachang.rimdb.index.number64.CreateNumber64Index;
import org.maachang.rimdb.index.numberFloat.CreateNumberFloatIndex;
import org.maachang.rimdb.index.position.CreatePositionIndex;
import org.maachang.rimdb.index.position.PositionIndex;
import org.maachang.rimdb.index.string.CreateStringIndex;
import org.maachang.rimdb.table.array.BooleanArray;
import org.maachang.rimdb.table.array.ColumnArray;
import org.maachang.rimdb.table.array.DateArray;
import org.maachang.rimdb.table.array.FloatArray;
import org.maachang.rimdb.table.array.IntegerArray;
import org.maachang.rimdb.table.array.LongArray;
import org.maachang.rimdb.table.array.ObjectArray;
import org.maachang.rimdb.table.array.StringArray;
import org.maachang.rimdb.table.array.TimeArray;
import org.maachang.rimdb.table.array.TimestampArray;
import org.maachang.rimdb.table.array.UniqueId;
import org.maachang.rimdb.util.ConvertUtil;
import org.maachang.rimdb.util.OList;

/**
 * テーブル作成.
 * 
 * @version 2014/07/09
 * @author  masahito suzuki
 * @since   rimdb-1.00
 */
@SuppressWarnings("unchecked")
public final class CreateTable {
    
    private final String name ;
    private final int[] types ;
    private final String[] names ;
    private final String[] uniqueNames ;
    private final String[] indexNames ;
    private final String[] ngramNames ;
    private final String positionX ;
    private final String positionY ;
    private final int accuracy ;
    private final ColumnName[] columnNames ;
    private final OList<Object[]> list ;
    private final int length ;
    private int position ;
    
    /**
     * テーブル作成.
     * @param name テーブル名を設定します.
     * @param names カラム名群を設定します.
     * @param types カラムタイプ群を設定します.
     * @param uniqueNames ユニーク定義カラム名群を設定します.
     * @param indexNames インデックス定義カラム名群を設定します.
     * @param ngramNames NGramインデックス定義カラム名群を設定します.
     * @param posX 空間インデックス名のX軸カラム名を設定します.
     * @param posY 空間インデックス名のY軸カラム名を設定します.
     * @param accuracy 空間インデックス精度を設定します.
     */
    public CreateTable( final String name,final String[] names,final int[] types,
        String[] uniqueNames,String[] indexNames,String[] ngramNames,String positionX,
        String positionY,int accuracy ) {
        
        final int len = types.length ;
        ColumnName[] cc = new ColumnName[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            cc[ i ] = new ColumnName( names[ i ],i ) ;
        }
        
        Arrays.sort( cc ) ;
        if( ngramNames != null && ngramNames.length > 0 ) {
            Arrays.sort( ngramNames ) ;
        }
        
        this.name = name ;
        this.names = names ;
        this.types = types ;
        this.uniqueNames = uniqueNames ;
        this.indexNames = indexNames ;
        this.ngramNames = ngramNames ;
        if( positionX != null && positionY != null ) {
            this.positionX = positionX ;
            this.positionY = positionY ;
            this.accuracy = ( accuracy <= 0 ) ? 1 : accuracy ;
        }
        else {
            this.positionX = null ;
            this.positionY = null ;
            this.accuracy = -1 ;
        }
        this.columnNames = cc ;
        this.length = len ;
        this.list = new OList<Object[]>() ;
        this.position = -1 ;
    }
    
    /**
     * テーブル名を取得.
     * @return String テーブル名が返却されます.
     */
    public final String getName() {
        return name ;
    }
    
    /**
     * カラム長を取得.
     * @return int カラム長が返却されます.
     */
    public final int length() {
        return length ;
    }
    
    /**
     * カラム名一覧を取得.
     * @return String[] カラム名一覧が返却されます.
     */
    public final String[] getColumns() {
        return names ;
    }
    
    /**
     * 次の行に移動.
     */
    public final void next() {
        list.add( new Object[ length ] ) ;
        position ++ ;
    }
    
    /**
     * 現在の設定ポジションを取得.
     * @return int 現在の設定ポジションが返却されます.
     */
    public final int position() {
        return position ;
    }
    
    /**
     * 情報をセット.
     * @param value 情報を設定します.
     */
    public final void set( final Object... value ) {
        final int len = value.length ;
        System.arraycopy( value,0,list.get( position ),0,len ) ;
    }
    
    /**
     * 情報をセット.
     * @param no 対象のカラム項番を設定します.
     * @param value 対象の情報を設定します.
     */
    public final void set( final int no,final Object value ) {
        list.get( position )[ no ] = value ;
    }
    
    /**
     * 情報をセット.
     * @param name 対象のカラム名を設定します.
     * @param value 対象の情報を設定します.
     */
    public final void set( final String name,final Object value ) {
        final int p = IndexUtil.search( columnNames,name ) ;
        if( p != -1 ) {
            list.get( position )[ p ] = value ;
        }
    }
    
    /**
     * テーブル情報を生成.
     * @return Table テーブル情報を生成します.
     */
    public final TableImpl getTable() {
        
        try {
            
            // データ件数がゼロ件の場合.
            if( position == -1 ) {
                
                // ゼロ件のテーブルを作成.
                return new TableImpl( new Columns( null,types,columnNames ),null,null,name,0 ) ;
            }
            
            int i,j ;
            final int len = list.size() ;
            final int[] typeList = types ;
            final int columnLength = length ;
            
            // カラム情報を作成.
            Object[] columnList = new Object[ columnLength ] ;
            for( i = 0 ; i < columnLength ; i ++ ) {
                columnList[ i ]  = createArray( typeList[ i ],len,i ) ;
            }
            
            // 対象条件をカラムセット.
            Object[] rows ;
            for( i = 0 ; i < len ; i ++ ) {
                rows = list.get( i ) ;
                for( j = 0 ; j < columnLength ; j ++ ) {
                    setArray( columnList[ j ],typeList[ j ],i,rows[ j ] ) ;
                }
            }
            rows = null ;
            list.clear() ;
            position = -1 ;
            
            // 各Array条件を生成.
            ColumnArray[] arrays = new ColumnArray[ columnLength ] ;
            for( i = 0 ; i < columnLength ; i ++ ) {
                arrays[ i ]  = createColumnArray( typeList[ i ],columnList[ i ] ) ;
            }
            
            // カラムテーブル情報を作成.
            final Columns columnTable = new Columns( arrays,typeList,columnNames ) ;
            arrays = null ;
            
            // ユニーク条件を作成.
            Uniques uniqueTable ;
            if( uniqueNames != null && uniqueNames.length > 0 ) {
                
                // ユニーク条件のカラム番号を取得.
                final int[] uniqueColumnNo = createColumnNoList(
                    typeList,new int[]{ColumnType.TYPE_INT,ColumnType.TYPE_LONG},names,uniqueNames ) ;
                
                // カラム番号をソート.
                Arrays.sort( uniqueColumnNo ) ;
                
                // ユニーク情報を作成.
                final int uniqueLength = uniqueColumnNo.length ;
                final UniqueId[] uniqueList = new UniqueId[ uniqueLength ] ;
                for( i = 0 ; i < uniqueLength ; i ++ ) {
                    uniqueList[ i ] = createUniqueId( typeList[ uniqueColumnNo[ i ] ],columnList[ uniqueColumnNo[ i ] ] ) ;
                }
                uniqueTable = new Uniques( uniqueColumnNo,uniqueList ) ;
                
            }
            else {
                uniqueTable = null ;
            }
            
            // インデックス条件を作成.
            Index[] indexList ;
            int[] indexColumnNo ;
            if( indexNames != null && indexNames.length > 0 ) {
                
                // インデックス条件のカラム番号を取得.
                indexColumnNo = createColumnNoList( typeList,null,names,indexNames ) ;
                
                // カラム番号をソート.
                Arrays.sort( indexColumnNo ) ;
                
                // インデックス情報を作成.
                final int indexLength = indexColumnNo.length ;
                indexList = new Index[ indexLength ] ;
                
                // NGramインデックス定義が存在する場合.
                if( ngramNames != null && ngramNames.length > 0 ) {
                    for( i = 0 ; i < indexLength ; i ++ ) {
                        if( IndexUtil.search( ngramNames,columnNames[ indexColumnNo[ i ] ].name ) != -1 ) {
                            indexList[ i ] = createIndex(
                                columnNames[ indexColumnNo[ i ] ].name,
                                typeList[ indexColumnNo[ i ] ],columnList[ indexColumnNo[ i ] ],true ) ;
                        }
                        else {
                            indexList[ i ] = createIndex(
                                columnNames[ indexColumnNo[ i ] ].name,
                                typeList[ indexColumnNo[ i ] ],columnList[ indexColumnNo[ i ] ],false ) ;
                        }
                    }
                }
                // NGramインデックス定義が存在しない場合.
                else {
                    for( i = 0 ; i < indexLength ; i ++ ) {
                        indexList[ i ] = createIndex(
                            columnNames[ indexColumnNo[ i ] ].name,
                            typeList[ indexColumnNo[ i ] ],columnList[ indexColumnNo[ i ] ],false ) ;
                    }
                }
            }
            else {
                indexList = null ;
                indexColumnNo = null ;
            }
            
            // 空間インデックス情報を作成.
            PositionIndex posIdx ;
            int[] positionColumnNo ;
            if( positionX != null && positionY != null ) {
                
                // 空間インデックスのカラム番号を取得.
                final int s = getColumnNo( names,positionX ) ;
                final int e = getColumnNo( names,positionY ) ;
                if( s == -1 || e == -1 ) {
                    throw new RimDbException( "空間インデックスのカラム名[x=" + positionX + ":" + s +
                        "][y=" + positionY + ":" + e + "]は存在しません" ) ;
                }
                if( columnTable.list[ s ].getType() != ColumnType.TYPE_INT ) {
                    throw new RimDbException( "空間インデックスX条件[" + positionX + "]のカラムタイプはintではありません" ) ;
                }
                if( columnTable.list[ e ].getType() != ColumnType.TYPE_INT ) {
                    throw new RimDbException( "空間インデックスY条件[" + positionY + "]のカラムタイプはintではありません" ) ;
                }
                
                // ポジションインデックス情報を作成.
                CreatePositionIndex n = new CreatePositionIndex( accuracy,
                    (IntegerArray)columnTable.list[ s ],(IntegerArray)columnTable.list[ e ] ) ;
                posIdx = n.create() ;
                
                positionColumnNo = new int[ 2 ] ;
                positionColumnNo[ 0 ] = s ;
                positionColumnNo[ 1 ] = e ;
            }
            else {
                posIdx = null ;
                positionColumnNo = null ;
            }
            
            // インデックステーブルを作成.
            Indexs indexTable = new Indexs( columnTable,indexColumnNo,indexList,positionColumnNo,posIdx ) ;
            
            // テーブルオブジェクトを返却.
            return new TableImpl( columnTable,uniqueTable,indexTable,name,len ) ;
            
        } catch( Exception e ) {
            throw new RimDbException( "テーブル[" + name + "]の作成に失敗しました",e ) ;
        }
    }
    
    /** 指定カラム名に対汁カラム項番を取得. **/
    private static final int getColumnNo( final String[] columnNames,final String column ) {
        final int len = columnNames.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( columnNames[ i ].equals( column ) ) {
                return i ;
            }
        }
        return -1 ;
    }
    
    /** 指定カラム名に対する、カラム項番を作成. **/
    private static final int[] createColumnNoList( final int[] types,final int[] target,
        final String[] columnNames,final String[] value ) {
        int i,j,t ;
        String n ;
        final int len = value.length ;
        final int allLen = columnNames.length ;
        final int targetLen = ( target != null ) ? target.length : 0 ;
        final int[] ret = new int[ len ] ;
        for( i = 0 ; i < len ; i ++ ) {
            ret[ i ] = -1 ;
            n = value[ i ] ;
            for( j = 0 ; j < allLen ; j ++ ) {
                if( columnNames[ j ].equals( n ) ) {
                    ret[ i ] = j ;
                    break ;
                }
            }
            if( ret[ i ] == -1 ) {
                throw new RimDbException( "指定条件のカラム名[" + n + "]は存在しません" ) ;
            }
            if( target != null ) {
                t = types[ ret[ i ] ] ;
                for( j = 0 ; j < targetLen ; j ++ ) {
                    if( t == target[ j ] ) {
                        t = -1 ;
                    }
                }
                if( t != -1 ) {
                    throw new RimDbException( "指定条件のカラムタイプ[" + t + "]は許可されていません" ) ;
                }
            }
        }
        return ret ;
    }
    
    /** カラム配列オブジェクトを生成. **/
    private static final Object createArray( final int type,final int len,final int no ) {
        switch( type ) {
            case ColumnType.TYPE_BOOL :
                return new Boolean[ len ] ;
            case ColumnType.TYPE_INT :
                return new Integer[ len ] ;
            case ColumnType.TYPE_LONG :
                return new Long[ len ] ;
            case ColumnType.TYPE_FLOAT :
                return new Double[ len ] ;
            case ColumnType.TYPE_DATE :
                return new java.sql.Date[ len ] ;
            case ColumnType.TYPE_TIME :
                return new java.sql.Time[ len ] ;
            case ColumnType.TYPE_TIMESTAMP :
                return new java.sql.Timestamp[ len ] ;
            case ColumnType.TYPE_STRING :
                return new String[ len ] ;
            case ColumnType.TYPE_OBJECT :
                return new Object[ len ] ;
        }
        throw new RimDbException( "型[" + type + "]が不明です("+no+")" ) ;
    }
    
    /** カラム配列オブジェクトを設定. **/
    private static final void setArray( final Object out,final int type,final int no,final Object v ) {
        switch( type ) {
            case ColumnType.TYPE_BOOL :
                ((Boolean[])out)[ no ] = ConvertUtil.convertBool( v ) ;
                break ;
            case ColumnType.TYPE_INT :
                ((Integer[])out)[ no ] = ConvertUtil.convertInt( v ) ;
                break ;
            case ColumnType.TYPE_LONG :
                ((Long[])out)[ no ] = ConvertUtil.convertLong( v ) ;
                break ;
            case ColumnType.TYPE_FLOAT :
                ((Double[])out)[ no ] = ConvertUtil.convertDouble( v ) ;
                break ;
            case ColumnType.TYPE_DATE :
                ((java.sql.Date[])out)[ no ] = ConvertUtil.convertSqlDate( v ) ;
                break ;
            case ColumnType.TYPE_TIME :
                ((java.sql.Time[])out)[ no ] = ConvertUtil.convertSqlTime( v ) ;
                break ;
            case ColumnType.TYPE_TIMESTAMP :
                ((java.sql.Timestamp[])out)[ no ] = ConvertUtil.convertSqlTimestamp( v ) ;
                break ;
            case ColumnType.TYPE_STRING :
                ((String[])out)[ no ] = ConvertUtil.convertString( v ) ;
                break ;
            case ColumnType.TYPE_OBJECT :
                ((Object[])out)[ no ] = v ;
                break ;
        }
    }
    
    /** ColumnArrayオブジェクトを生成. **/
    private static final ColumnArray createColumnArray( final int type,Object v ) {
        switch( type ) {
            case ColumnType.TYPE_BOOL :
                return new BooleanArray( ( Boolean[] )v ) ;
            case ColumnType.TYPE_INT :
                return new IntegerArray( ( Integer[] )v ) ;
            case ColumnType.TYPE_LONG :
                return new LongArray( ( Long[] )v ) ;
            case ColumnType.TYPE_FLOAT :
                return new FloatArray( ( Double[] )v ) ;
            case ColumnType.TYPE_DATE :
                return new DateArray( ( java.sql.Date[] )v ) ;
            case ColumnType.TYPE_TIME :
                return new TimeArray( ( java.sql.Time[] )v ) ;
            case ColumnType.TYPE_TIMESTAMP :
                return new TimestampArray( ( java.sql.Timestamp[] )v ) ;
            case ColumnType.TYPE_STRING :
                return new StringArray( ( String[] )v ) ;
            case ColumnType.TYPE_OBJECT :
                return new ObjectArray( ( Object[] )v ) ;
        }
        return null ;
    }
    
    /** UniqueIdを作成. **/
    private static final UniqueId createUniqueId( final int type,final Object value ) {
        switch( type ) {
            case ColumnType.TYPE_INT :
                return new UniqueId( ( Integer[] )value ) ;
            case ColumnType.TYPE_LONG :
                return new UniqueId( ( Long[] )value ) ;
        }
        throw new RimDbException( "ユニーク条件は[int,long]以外のカラムは適応できません" ) ;
    }
    
    /** インデックスを作成. **/
    private static final Index createIndex( final String column,final int type,final Object value,final boolean ngram ) {
        try {
            switch( type ) {
                case ColumnType.TYPE_BOOL :
                {
                    CreateBoolIndex idx = new CreateBoolIndex() ;
                    idx.addArray( (Boolean[])value ) ;
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_INT :
                {
                    CreateNumber32Index idx = new CreateNumber32Index() ;
                    idx.addArray( (Integer[])value ) ;
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_LONG :
                {
                    CreateNumber64Index idx = new CreateNumber64Index() ;
                    idx.addArray( (Long[])value ) ;
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_FLOAT :
                {
                    CreateNumberFloatIndex idx = new CreateNumberFloatIndex() ;
                    idx.addArray( (Double[])value ) ;
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_DATE :
                {
                    CreateNumber64Index idx = new CreateNumber64Index() ;
                    java.sql.Date[] d = (java.sql.Date[])value ;
                    final int len = d.length ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        idx.add( d[ i ].getTime() ) ;
                    }
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_TIME :
                {
                    CreateNumber32Index idx = new CreateNumber32Index() ;
                    java.sql.Time[] t = (java.sql.Time[])value ;
                    final int len = t.length ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        idx.add( (int)( t[ i ].getTime() ) ) ;
                    }
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_TIMESTAMP :
                {
                    CreateNumber64Index idx = new CreateNumber64Index() ;
                    java.sql.Timestamp[] t = (java.sql.Timestamp[])value ;
                    final int len = t.length ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        idx.add( t[ i ].getTime() ) ;
                    }
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_STRING :
                {
                    CreateStringIndex idx = new CreateStringIndex() ;
                    idx.addArray( (String[])value ) ;
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
                case ColumnType.TYPE_OBJECT :
                {
                    CreateComparableIndex idx = new CreateComparableIndex() ;
                    final int len = Array.getLength( value ) ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        idx.add( (Comparable)Array.get( value,i ) ) ;
                    }
                    idx.setNGram( ngram ) ;
                    return idx.create() ;
                }
            }
            return null ;
        } catch( Exception e ) {
            throw new RimDbException( "カラム[" + column + "]のインデックス作成に失敗しました",e ) ;
        }
    }
}
