package org.maachang.dao.dbms;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;

import org.maachang.util.FileUtil;
import org.maachang.util.SerializableSequenceList;

/**
 * Javaシーケンス発行オブジェクト.
 * <p>データベースのシーケンス発行を利用せず、
 * Javaシーケンス発行で処理を行います。</p>
 * 
 * @version 2008/11/04
 * @author masahito suzuki
 * @since MaachangDao 1.11
 */
public class RecordSequenceId {
    
    /**
     * デフォルトシーケンス発行長.
     */
    private static final int DEFAULT_LENGTH = 32 ;
    
    /**
     * シーケンス発行オブジェクト.
     */
    private SerializableSequenceList sequence = null ;
    
    /**
     * メタデータ管理.
     */
    private RecordSequenceMeta meta = null ;
    
    /**
     * コンストラクタ.
     */
    private RecordSequenceId() {
        
    }
    
    /**
     * コンストラクタ.
     * @param fileName 対象のファイル名を設定します.
     * @param metaFileName 対象のメタファイル名を設定します.
     * @exception Exception 例外.
     */
    public RecordSequenceId( String fileName,String metaFileName )
        throws Exception {
        if( fileName == null || ( fileName.trim() ).length() <= 0 ||
            metaFileName == null || ( metaFileName = metaFileName.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.sequence = new SerializableSequenceList( DEFAULT_LENGTH,fileName ) ;
        this.meta = new RecordSequenceMeta( metaFileName ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * デストラクタ.
     */
    public synchronized void destroy() {
        if( sequence != null ) {
            sequence.destroy() ;
        }
        if( meta != null ) {
            meta.destroy() ;
        }
        sequence = null ;
        meta = null ;
    }
    
    /**
     * シーケンスID内容をクリア.
     * @exception Exception 例外.
     */
    public synchronized void clearSequence()
        throws Exception {
        int len = sequence.length() ;
        for( int i = 1 ; i < len ; i ++ ) {
            sequence.set( i,0L ) ;
        }
    }
    
    /**
     * 全てをクリア.
     * @exception Exception 例外.
     */
    public synchronized void allClear()
        throws Exception {
        int len = sequence.length() ;
        for( int i = 0 ; i < len ; i ++ ) {
            sequence.set( i,0L ) ;
        }
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合有効です.
     */
    public synchronized boolean isUse() {
        return ( sequence != null && sequence.isUse() &&
            meta != null && meta.isUse() ) ;
    }
    
    /** 指定名に対する項番取得. **/
    private int getNo( String name ) throws Exception {
        int ret ;
        if( meta.containsKey( name ) == false ) {
            int newNo = ( int )sequence.get( 0 ) ;
            if( newNo <= 0 ) {
                newNo = 1 ;
            }
            else {
                newNo ++ ;
            }
            sequence.set( 0,( long )newNo ) ;
            if( sequence.length() <= newNo ) {
                sequence.addSpace( DEFAULT_LENGTH ) ;
            }
            meta.put( name,newNo ) ;
            ret = newNo ;
        }
        else {
            ret = meta.get( name ) ;
        }
        return ret ;
    }
    
    /**
     * 通常テーブル用シーケンスIDを設定.
     * @param name 対象のテーブル名を設定します.
     * @param seq 対象のシーケンスIDを設定します.
     * @exception Exception 例外.
     */
    public synchronized void setId( String name,long seq )
        throws Exception {
        if( seq <= -1L ) {
            throw new IllegalArgumentException( "指定シーケンスIDは不正です:" + seq ) ;
        }
        int no = getNo( name ) ;
        long now = sequence.get( no ) ;
        if( now < seq ) {
            sequence.set( no,seq ) ;
        }
    }
    
    /**
     * 通常テーブル用シーケンスIDを取得.
     * @param name 対象のテーブル名を設定します.
     * @return long シーケンスIDが返されます.
     * @exception Exception 例外.
     */
    public synchronized long getId( String name )
        throws Exception {
        int no = getNo( name ) ;
        return sequence.getId( no ) ;
    }
    
    /**
     * 指定名のシーケンスをクリア.
     * @param name クリア対象のシーケンスを設定します.
     * @exception Exception 例外.
     */
    public void clearSequence( String name )
        throws Exception {
        int no = getNo( name ) ;
        sequence.set( no,0L ) ;
    }
}

/**
 * シーケンス割り当てメタ情報.
 */
class RecordSequenceMeta {
    private Map<String,Integer> meta = null ;
    private RandomAccessFile writeRandomFp = null ;
    private RecordSequenceMeta() {}
    
    public RecordSequenceMeta( String name ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        Map<String,Integer> meta = null ;
        RandomAccessFile writeRandomFp = null ;
        if( FileUtil.isFileExists( name ) ) {
            meta = load( name ) ;
            writeRandomFp = new RandomAccessFile( name,"rwd" ) ;
            writeRandomFp.seek( writeRandomFp.length() ) ;
        }
        else {
            meta = new HashMap<String,Integer>() ;
            writeRandomFp = new RandomAccessFile( name,"rwd" ) ;
        }
        this.meta = meta ;
        this.writeRandomFp = writeRandomFp ;
    }
    
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    private Map<String,Integer> load( String name ) throws Exception {
        BufferedReader br = null ;
        Map<String,Integer> ret = null ;
        try {
            ret = new HashMap<String,Integer>() ;
            br = new BufferedReader( new InputStreamReader( new FileInputStream( name ) ) ) ;
            for( ;; ) {
                String s = br.readLine() ;
                if( s == null ) {
                    break ;
                }
                if( ( s = s.trim() ).length() <= 0 ) {
                    continue ;
                }
                int p = s.indexOf( "=" ) ;
                if( p <= -1 ) {
                    continue ;
                }
                boolean flg = false ;
                String key = null ;
                Integer value = null ;
                try {
                    key = s.substring( 0,p ) ;
                    value = new Integer( s.substring( p+1 ) ) ;
                } catch( Exception e ) {
                    flg = true ;
                }
                if( flg ) {
                    continue ;
                }
                ret.put( key,value ) ;
            }
            br.close() ;
            br = null ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
            br = null ;
        }
        return ret ;
    }
    
    public void destroy() {
        if( writeRandomFp != null ) {
            try {
                writeRandomFp.close() ;
            } catch( Exception e ) {
            }
        }
        meta = null ;
        writeRandomFp = null ;
    }
    
    public boolean isUse() {
        return ( writeRandomFp != null ) ;
    }
    
    public void put( String metaName,int pos )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( metaName == null || metaName.length() <= 0 || pos <= -1 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        metaName = DbUtil.convertJavaNameByDBName( metaName ) ;
        if( meta.containsKey( metaName ) == false ) {
            writeRandomFp.write( new StringBuilder().append( metaName ).
                append( "=" ).append( pos ).append( "\n" ).toString().getBytes() ) ;
            meta.put( metaName,new Integer( pos ) ) ;
        }
    }
    
    public int get( String metaName )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( metaName == null || metaName.length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        metaName = DbUtil.convertJavaNameByDBName( metaName ) ;
        Integer ret = meta.get( metaName ) ;
        if( ret == null ) {
            return -1 ;
        }
        return ret.intValue() ;
    }
    
    public boolean containsKey( String metaName )
        throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( metaName == null || metaName.length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        metaName = DbUtil.convertJavaNameByDBName( metaName ) ;
        return meta.containsKey( metaName ) ;
    }
}

