package org.maachang.comet ;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.ArrayList;

import org.maachang.comet.httpd.engine.script.image.ImageManager;
import org.maachang.comet.httpd.engine.script.image.ImageOp;
import org.maachang.conf.Config;
import org.maachang.conf.ConvIniParam;
import org.maachang.conf.ReadIni;
import org.maachang.dao.dbms.DbUtil;
import org.maachang.dbm.MDbm;
import org.maachang.dbm.MDbmManager;

/**
 * 指定CSVをMDBM内にインデックス化する.
 * 
 * @version 2007/09/03
 * @author masahito suzuki
 * @since MaachangComet 1.14
 */
public class CsvToIndexMdbm {
    private static final String ARGS_CSV = "-f" ;
    private static final String ARGS_HEADER = "-h" ;
    private static final String ARGS_KEY_HEAD = "-k" ;
    private static final String ARGS_INDEX = "-i" ;
    private static final String ARGS_MODE = "-m" ;
    private static final String ARGS_CHARSET = "-c" ;
    
    private static final String MODE_CONNECT_REMOTE = "R" ;
    private static final String DEFAULT_CHARSET = "WINDOWS-31J" ;
    
    public static final void main( String[] args ) throws Exception {
        CsvToIndexMdbmBean bean = getCsvToIndexMdbmBean( args ) ;
        if( bean == null ) {
            viewHelp() ;
            return ;
        }
        System.out.println( "*** Csv2Index by MDBM. ***" ) ;
        System.out.println() ;
        System.out.println( "csvFile:" + bean.getCsv() ) ;
        System.out.println( "keyHead:" + bean.getKeyHead() ) ;
        System.out.println() ;
        
        if( bean.getHead() != null ) {
            if( bean.getIndex() <= -1 && bean.getHead() == null) {
                viewHelp() ;
                return ;
            }
            System.out.println( "headFile:" + bean.getHead() ) ;
            System.out.println( "index:" + bean.getIndex() ) ;
        }
        else {
            System.out.println( "headFile:直接入力モード" ) ;
        }
        System.out.println( "charset:" + bean.getCharset() ) ;
        System.out.println() ;
        
        // MDBMを取得.
        MDbm mdbm = null ;
        if( bean.getMode() != null && bean.getMode().startsWith( MODE_CONNECT_REMOTE ) ) {
            System.out.println( "MDBMに対して、リモート接続で処理します" ) ;
            mdbm = connectMDBM() ;
        }
        else {
            System.out.println( "MDBMに対して、直接接続で処理します" ) ;
            mdbm = localMDBM() ;
        }
        if( mdbm == null ) {
            System.out.println( "MDBMのオープンに失敗しました." ) ;
        }
        System.out.println() ;
        
        // ヘッダ情報を取得.
        if( bean.getHead() != null ) {
            getHeader( bean ) ;
        }
        
        // 実行処理.
        BufferedReader br = null ;
        try {
            br = new BufferedReader( new InputStreamReader( new FileInputStream( bean.getCsv() ),bean.getCharset() )  ) ;
            long time = System.currentTimeMillis() ;
            long nextTime = System.currentTimeMillis() ;
            int cnt = 0 ;
            int viewCnt = 0 ;
            int errorCnt = 0 ;
            System.out.print( "進行:" ) ;
            for( ;; ) {
                String one = br.readLine() ;
                if( one == null ) {
                    break ;
                }
                else if( ( one = one.trim() ).length() <= 0 ) {
                    continue ;
                }
                String data = null ;
                String key = null ;
                // csv指定の場合.
                if( bean.getHead() != null ) {
                    String[] headerKeys = bean.getHeaderData() ;
                    ArrayList<String> olst = DbUtil.cutCsv( false,one,"," ) ;
                    if( olst == null || olst.size() != headerKeys.length ) {
                        errorCnt ++ ;
                        continue ;
                    }
                    int len = olst.size() ;
                    StringBuilder buf = new StringBuilder() ;
                    buf.append( "[{" ) ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( i != 0 ) {
                            buf.append( "," ) ;
                        }
                        buf.append( "\'" ).append( headerKeys[ i ] ).append( "\':" ) ;
                        String z = olst.get( i ) ;
                        z = z.trim() ;
                        if( z.length() > 0 && z.startsWith( "\'" ) == false ) {
                            if( z.startsWith( "new Date(" ) ) {
                                buf.append( olst.get( i ) ) ;
                            }
                            else {
                                buf.append( "\'" ).append( olst.get( i ) ).append( "\'" ) ;
                            }
                        }
                        else {
                            buf.append( olst.get( i ) ) ;
                        }
                    }
                    buf.append( "}];" ) ;
                    data = buf.toString() ;
                    key = olst.get( bean.getIndex() ).trim() ;
                }
                // 直接指定の場合.
                // KEYは必ず１番目に存在しなければならない.
                // また、そのときKeyは削除されます.
                else {
                    int p = one.indexOf( "," ) ;
                    if( p <= -1 ) {
                        errorCnt ++ ;
                        continue ;
                    }
                    key = one.substring( 0,p ) ;
                    key = key.trim() ;
                    data = one.substring( p+1 ).trim() ;
                    if( data.startsWith( "[" ) == false && data.endsWith( "]" ) == false ) {
                        data = new StringBuilder().append( "[" ).append( data ).append( "]" ).toString() ;
                    }
                    if( data.endsWith( ";" ) ) {
                        data = data + ";" ;
                    }
                }
                
                if( ( key.startsWith( "\'" ) && key.endsWith( "\'" ) ) ||
                    ( key.startsWith( "\"" ) && key.endsWith( "\"" ) ) ) {
                    key = key.substring( 1,key.length()-1 ) ;
                }
                if( bean.getKeyHead() != null ) {
                    key = bean.getKeyHead() + key ;
                }
                
                //System.out.println( "key:" + key + " data:" + data ) ;
                
                mdbm.put( key.getBytes( "UTF8" ),data.getBytes( "UTF8" ) ) ;
                cnt ++ ;
                viewCnt ++ ;
                if( viewCnt >= 100 ) {
                    System.out.print( "■" ) ;
                    if( cnt >= 3000 && ( cnt % 3000 ) == 0 ) {
                        System.out.print( "-"+( System.currentTimeMillis()-nextTime)+"msec\n　　 " ) ;
                        nextTime = System.currentTimeMillis() ;
                    }
                    viewCnt = 0 ;
                }
            }
            System.out.println( "\n exit : " + cnt + " - " + ( System.currentTimeMillis()-time ) + "msec..." ) ;
            System.out.println( "error :" + errorCnt ) ;
            mdbm.close() ;
        } catch( Exception e ) {
            e.printStackTrace() ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception ee ) {
                }
            }
        }
    }
    
    /**
     * ヘルプ内容出力.
     */
    private static final void viewHelp() {
        System.out.println( "csv2mdbm [-f] [-k] [-h] [-i] [-m]" ) ;
        System.out.println( "     [-f] CSVファイルを指定します.          ※必須" ) ;
        System.out.println( "     [-k] Keyに付加するヘッダ名を設定します.※CSV:必須" ) ;
        System.out.println( "     [-h] ヘッダCSVファイルを設定します.    ※CSV:必須" ) ;
        System.out.println( "     [-i] Index項番を設定します.            ※CSV:必須" ) ;
        System.out.println( "     [-c] charsetを設定します.defaultは[SHITF_JIS]です." ) ;
        System.out.println( "     [-m] 接続モードを設定します." ) ;
        System.out.println( "          B 直接MDBMに接続します." ) ;
        System.out.println( "          R リモート接続します." ) ;
        System.out.println( "            また接続モードを設定しない場合は、Bと同じです." ) ;
        System.out.println() ;
        System.out.println( " また、[-h][-i]を除外した場合、１行データを直接データセット") ;
        System.out.println( " します.このときのIndexは、先頭からカンマまでの文字が、") ;
        System.out.println( " となります.そして、そのIndex値は格納データから除外されます.") ;
        System.out.println( " また、この条件は、JSON形式のデータを直接セットする場合に有効です.") ;
        System.out.println() ;
        System.out.println( " １行データをCSVデータセットする場合は、「※CSV:必須」個所が") ;
        System.out.println( " 必須入力となります.") ;
    }
    
    /**
     * 実行モードを取得.
     */
    private static final CsvToIndexMdbmBean getCsvToIndexMdbmBean( String[] args ) {
        if( args.length <= 0 || "--help".equals( args[ 0 ] ) ) {
            return null ;
        }
        CsvToIndexMdbmBean ret = new CsvToIndexMdbmBean() ;
        int len = args.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            String s = args[ i ] ;
            if( s == null || ( s = s.trim() ).length() <= 0 ) {
                continue ;
            }
            if( s.startsWith( ARGS_CSV ) ) {
                s = s.substring( ARGS_CSV.length() ) ;
                if( ( s = s.trim() ).length() <= 0 ) {
                    return null ;
                }
                ret.setCsv( s ) ;
            }
            else if( s.startsWith( ARGS_HEADER ) ) {
                s = s.substring( ARGS_HEADER.length() ) ;
                if( ( s = s.trim() ).length() <= 0 ) {
                    return null ;
                }
                ret.setHead( s ) ;
            }
            else if( s.startsWith( ARGS_INDEX ) ) {
                s = s.substring( ARGS_INDEX.length() ) ;
                if( ( s = s.trim() ).length() <= 0 ) {
                    return null ;
                }
                int x = ConvIniParam.getInt( s ) ;
                if( x <= -1 ) {
                    return null ;
                }
                ret.setIndex( x ) ;
            }
            else if( s.startsWith( ARGS_KEY_HEAD ) ) {
                s = s.substring( ARGS_KEY_HEAD.length() ) ;
                if( ( s = s.trim() ).length() <= 0 ) {
                    return null ;
                }
                if( s.endsWith( "." ) == false ) {
                    s = s+"." ;
                }
                ret.setKeyHead( s ) ;
            }
            
            else if( s.startsWith( ARGS_MODE ) ) {
                s = s.substring( ARGS_MODE.length() ) ;
                if( ( s = s.trim() ).length() > 0 ) {
                    ret.setMode( s ) ;
                }
            }
            else if( s.startsWith( ARGS_CHARSET ) ) {
                s = s.substring( ARGS_CHARSET.length() ) ;
                if( ( s = s.trim() ).length() > 0 ) {
                    ret.setCharset( s ) ;
                }
            }
        }
        if( ret.getCsv() == null ) {
            return null ;
        }
        if( ret.getCharset() == null ) {
            ret.setCharset( DEFAULT_CHARSET ) ;
        }
        return ret ;
    }
    
    /**
     * MDBMローカル接続.
     */
    private static final MDbm localMDBM() throws Exception {
        BufferedReader br = null ;
        try {
            br = new BufferedReader( new InputStreamReader( new FileInputStream( "./conf/mdbm.conf" ),"UTF8" )  ) ;
            Config conf = new Config() ;
            ReadIni.analisys( conf,br ) ;
            br.close() ;
            br = null ;
            MDbmManager.getInstance().open( conf.get( "mdbm","directory",0 ) ) ;
            ImageOp op = ImageManager.getImageOp( false ) ;
            return op.getMDbm() ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * MDBMコネクション接続.
     */
    private static final MDbm connectMDBM() throws Exception {
        BufferedReader br = null ;
        try {
            br = new BufferedReader( new InputStreamReader( new FileInputStream( "./conf/mdbm.conf" ),"UTF8" )  ) ;
            Config conf = new Config() ;
            ReadIni.analisys( conf,br ) ;
            br.close() ;
            br = null ;
            if( ConvIniParam.getBoolean( conf.get( "mdbm","flag",0 ) ) == false ) {
                throw new IOException( "接続モードはOFFになっています" ) ;
            }
            ImageOp op = ImageManager.getImageOp(InetAddress.getByName( "127.0.0.1" ),
                conf.getInt( "mdbm","bind-port",0 ) ) ;
            return op.getMDbm() ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * ヘッダ情報を取得.
     */
    private static final void getHeader( CsvToIndexMdbmBean bean ) throws Exception {
        BufferedReader br = null ;
        try {
            br = new BufferedReader( new InputStreamReader( new FileInputStream( bean.getHead() ),bean.getCharset() )  ) ;
            for( ;; ) {
                String s = br.readLine() ;
                if( s == null ) {
                    break ;
                }
                if( ( s = s.trim() ).length() <= 0 ) {
                    continue ;
                }
                ArrayList<String> lst = DbUtil.cutCsv( false,s,"," ) ;
                int len = lst.size() ;
                if( len <= 0 ) {
                    throw new IOException( "ヘッダＣＳＶのサイズ不正" ) ;
                }
                String[] headerLst = new String[ len ] ;
                for( int i = 0 ; i < len ; i ++ ) {
                    String name = DbUtil.convertDBNameByJavaName( false,lst.get( i ) ) ;
                    headerLst[ i ] = name ;
                }
                bean.setHeaderData( headerLst ) ;
                break ;
            }
            br.close() ;
            br = null ;
            if( bean.getHead() == null ) {
                throw new IOException( "ヘッダ内容は不正です" ) ;
            }
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    
}

class CsvToIndexMdbmBean {
    private String csv = null ;
    private String head = null ;
    private int index = -1 ;
    private String keyHead = null ;
    private String mode = null ;
    private String charset = null ;
    private String[] headrData = null ;
    
    public void setCsv( String csv ) {
        this.csv = csv ;
    }
    public String getCsv() {
        return csv ;
    }
    public void setHead( String head ) {
        this.head = head ;
    }
    public String getHead() {
        return head ;
    }
    public void setIndex( int index ) {
        this.index = index ;
    }
    public int getIndex() {
        return index ;
    }
    public void setKeyHead( String keyHead ) {
        this.keyHead = keyHead ;
    }
    public String getKeyHead() {
        return keyHead ;
    }
    public void setMode( String mode ) {
        this.mode = mode ;
    }
    public String getMode() {
        return mode ;
    }
    public void setCharset( String charset ) {
        this.charset = charset ;
    }
    public String getCharset() {
        return charset ;
    }
    public void setHeaderData( String[] headrData ) {
        this.headrData = headrData ;
    }
    public String[] getHeaderData() {
        return headrData ;
    }
}
