package org.maachang.mimdb.core ;

import java.util.Arrays;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.impl.MimdbUtils;
import org.maachang.mimdb.core.util.Config;
import org.maachang.mimdb.core.util.Csv;

/**
 * CSVファイルから、テーブル定義.
 * 
 * @version 2013/10/28
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
final class MimdbCsvTable {
    private MimdbCsvTable() {}
    
    /**
     * Confオブジェクトを設定して、テーブル定義.
     * @param baseFolder 基本フォルダーを設定します.
     * @param conf 対象のConfオブジェクトを定義します.
     * @return String 作成が成功したテーブル名が返却されます.
     * @exception Exception 例外.
     */
    public static final String create( String baseFolder,Config conf ) throws Exception {
        String csvName ;
        // 基本フォルダー定義が行われている場合は、対象CSVファイル名とマージ.
        if( baseFolder != null ) {
            baseFolder = MimdbUtils.getFullPath( baseFolder ) ;
            if( !baseFolder.endsWith( "/" ) ) {
                baseFolder += "/" ;
            }
            csvName = MimdbUtils.getFullPath( baseFolder + conf.getString( "csv","file",0 ) ) ;
        }
        // 基本フォルダー定義が行われていない場合は、そのままフルパス取得.
        else {
            csvName = MimdbUtils.getFullPath( conf.getString( "csv","file",0 ) ) ;
        }
        if( !MimdbUtils.isFile( csvName ) ) {
            throw new MimdbException( "指定CSVファイル[" + csvName + "]は存在しません" ) ;
        }
        String charset = conf.getString( "csv","charset",0 ) ;
        if( charset == null || ( charset = charset.trim() ).length() <= 0 ) {
            charset = "Windows-31J" ;
        }
        String cutCode = conf.getString( "csv","cut",0 ) ;
        if( cutCode == null || cutCode.length() <= 0 ) {
            cutCode = "," ;
        }
        
        int lineNo = -1 ;
        long id = -1 ;
        long beforeId = -1 ;
        Object[] orderList = null ;
        String tableName = null ;
        Csv csv = null ;
        try {
            
            // カラム定義を取得.
            tableName = MimdbTableInstance.getTableName( conf ) ;
            String[] columns = getColumnNames( conf ) ;
            int[] types = getColumnTypes( conf,tableName ) ;
            
            // mimdb共通を取得.
            String primary = MimdbTableInstance.getPrimaryKey( conf ) ;
            String[] index = MimdbTableInstance.getIndexNames( conf ) ;
            String[] ngram = MimdbTableInstance.getNGramIndexNames( conf ) ;
            String[] hash = MimdbTableInstance.getHashIndexNames( conf ) ;
            boolean compress = MimdbTableInstance.getCompressFlag( conf ) ;
            int compressLength = MimdbTableInstance.getCompressLength( conf ) ;
            
            // CSVファイルを生成.
            csv = new Csv( csvName,charset,cutCode ) ;
            
            // テーブル生成開始.
            beforeId = MimdbTableManager.getInstance().getId( tableName ) ;
            id = MimdbTableInstance.begin( tableName ) ;
            
            // テーブルオブジェクトを生成.
            MimdbTable table = new MimdbTable( id,tableName,4096,columns,types,
                primary,index,ngram,hash,compress,compressLength ) ;
            
            // CSV情報を読み込む.
            int columnsLength = columns.length ;
            lineNo = 1 ;
            // CSVヘッダ付きの条件で読み込む.
            if( conf.getBoolean( "csv","header",0 ) ) {
                boolean header = false ;
                Object[] headerArray = null ;
                int[] csvOrder = null ;
                while( csv.next() ) {
                    if( !header ) {
                        headerArray = csv.getObjects() ;
                        header = true ;
                        // ヘッダチェック+並び位置を計算.
                        csvOrder = headerOrder( columns,headerArray,tableName ) ;
                    }
                    else {
                        orderList = csv.getObjects() ;
                        table.add( orderArray( csvOrder,orderList,columnsLength,lineNo ++,tableName ) ) ;
                    }
                }
            }
            // CSVヘッダ無しの条件で読み込む.
            else {
                while( csv.next() ) {
                    orderList = csv.getObjects() ;
                    table.add( ( orderArray( null,orderList,columnsLength,lineNo ++,tableName ) ) ) ;
                }
            }
            csv.close() ;
            csv = null ;
            
            // テーブルFix.
            table.fix() ;
            
            // テーブル管理オブジェクトにテーブル登録.
            MimdbTableManager.getInstance().put( table ) ;
            
        } catch( Exception e ) {
            // エラーの場合は、バージョンIDを更新しない.
            String tblName = tableName ;
            if( beforeId != -1 ) {
                MimdbTableInstance.errorEnd( tableName,beforeId ) ;
            }
            tableName = null ;
            if( orderList != null ) {
                int len = orderList.length ;
                StringBuilder buf = new StringBuilder() ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( i != 0 ) {
                        buf.append( "," ) ;
                    }
                    buf.append( orderList[ i ] ) ;
                }
                throw new MimdbException( "[" + tblName + "]の読み込みに失敗しました(line:" + lineNo + ")\n[" + buf.toString() + "]",e ) ;
            }
            else {
                throw new MimdbException( "[" + tblName + "]の読み込みに失敗しました(line:" + lineNo + ")",e ) ;
            }
        } finally {
            // 処理終了.
            if( tableName != null ) {
                MimdbTableInstance.end( tableName ) ;
            }
            if( csv != null ) {
                csv.close() ;
            }
        }
        return tableName ;
    }
    
    /** 定義カラム名一覧を取得. **/
    private static final String[] getColumnNames( Config conf ) throws Exception {
        return conf.getKeys( "column" ) ;
    }
    
    /** 定義カラム型一覧を取得. **/
    private static final int[] getColumnTypes( Config conf,String tableName ) throws Exception {
        String[] columns = conf.getKeys( "column" ) ;
        if( columns == null || columns.length <= 0 ) {
            throw new MimdbException( "CSV定義[" + tableName + "]のカラム名は設定されていません" ) ;
        }
        int len = columns.length ;
        int[] ret = new int[ len ] ;
        String val ;
        for( int i = 0 ; i < len ; i ++ ) {
            val = conf.getString( "column",columns[ i ],0 ) ;
            if( val == null || ( val = val.trim() ).length() <= 0 ) {
                throw new MimdbException( "CSV定義[" + tableName + "]のカラム[" + columns[ i ] + "]に対する型定義は不正です" ) ;
            }
            if( "boolean".equals( val = val.toLowerCase() ) ) {
                ret[ i ] = MimdbIndex.COLUMN_BOOL ;
            }
            else if( "int".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_INT ;
            }
            else if( "long".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_LONG ;
            }
            else if( "float".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_FLOAT ;
            }
            else if( "double".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_FLOAT ;
            }
            else if( "string".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_STRING ;
            }
            else if( "varchar".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_STRING ;
            }
            else if( "date".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_DATE ;
            }
            else if( "time".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_TIME ;
            }
            else if( "timestamp".equals( val ) ) {
                ret[ i ] = MimdbIndex.COLUMN_TIMESTAMP ;
            }
            else {
                throw new MimdbException( "CSV定義[" + tableName + "]のカラム[" + columns[ i ] + "]に対する型定義[" + val + "]は不正です" ) ;
            }
        }
        return ret ;
    }
    
    /** ヘッダチェック+CSV並び順を取得. **/
    private static final int[] headerOrder( String[] columns,Object[] headerArray,String tableName )
        throws Exception {
        int len = headerArray.length ;
        if( columns.length != len ) {
            throw new MimdbException( "CSV定義[" + tableName + "]のカラム数が、CSVヘッダ数と一致しません" ) ;
        }
        int[] ret = new int[ len ] ;
        Arrays.fill( ret,-1 ) ;
        String n ;
        for( int i = 0 ; i < len ; i ++ ) {
            n = ( String )headerArray[ i ] ;
            for( int j = 0 ; j < len ; j ++ ) {
                if( n.equals( columns[ j ] ) ) {
                    if( ret[ i ] != -1 ) {
                        throw new MimdbException( "CSV定義[" + tableName + "]のカラム名[" + columns[ j ] + "]が複数定義されています" ) ;
                    }
                    ret[ i ] = j ;
                    break ;
                }
            }
        }
        for( int i = 0 ; i < len ; i ++ ) {
            if( ret[ i ] == -1 ) {
                throw new MimdbException( "CSV定義[" + tableName + "]のCSVヘッダ整合性が不正です" ) ;
            }
        }
        
        // 並び順がカラム定義と同一かチェック.
        int c = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( c == -1 ) {
                c = ret[ i ] ;
                continue ;
            }
            if( c + 1 != ret[ i ] ) {
                return ret ;
            }
            c = ret[ i ] ;
        }
        // 同一の場合はnullを返却.
        return null ;
    }
    
    /** データ内容のCSVヘッダ基準での並び替え. **/
    private static final Object[] orderArray( int[] order,Object[] values,int len,int no,String tableName )
        throws Exception {
        if( len != values.length ) {
            throw new MimdbException( "CSV定義[" + tableName + "]のカラム数が、CSV要素数と一致しません(行:" + no + ")" ) ;
        }
        if( order == null ) {
            return values ;
        }
        Object[] ret = new Object[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            ret[ order[ i ] ] = values[ i ] ;
        }
        return ret ;
    }
    
}
