package org.maachang.jni.io ;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * NativeRandomI/O
 * 
 * @version 2010/06/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.0
 */
public class NativeRandomIO {
    /** ファイルアクセスオプション : 読み込み専用. **/
    public static final int OPT_READ = NativeIODefine.OPT_READ ;
    /** ファイルアクセスオプション : 読み書き用. **/
    public static final int OPT_WRITE = NativeIODefine.OPT_RW ;
    /** ファイルアクセスモード ; ランダムアクセス. */
    protected static final int ACCESS_MODE = NativeIODefine.MODE_RAND ;
    
    protected long handle = -1L ;
    protected String name = null ;
    protected int opt = -1 ;
    
    /**
     * mmap管理.
     */
    protected final Map<Long,MmapBuffer> _mmapManager = new ConcurrentHashMap<Long,MmapBuffer>() ;
    protected final AtomicSeq _seq = new AtomicSeq( 0L ) ;
    
    
    /**
     * コンストラクタ.
     */
    protected NativeRandomIO() {}
    
    /**
     * コンストラクタ.
     * @param opt ファイルアクセスオプションを設定します.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public NativeRandomIO( int opt,String name ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( opt != OPT_READ ) {
            opt = OPT_WRITE ;
        }
        name = NativeIODefine.fullPath( name ) ;
        long handle = NativeIO.open( ACCESS_MODE,opt,NativeIODefine.nativeString( name ) ) ;
        if( handle <= -1L ) {
            throw new IOException( "ファイルオープンに失敗しました" ) ;
        }
        this.handle = handle ;
        this.name = name ;
        this.opt = opt ;
        // fsyncでファイル生成を確定.
        //if( NativeIO.fsync( handle ) != 0 ) {
        //    throw new IOException( "fsync処理に失敗" ) ;
        //}
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        close() ;
    }
    
    /**
     * クローズ処理.
     */
    public void close() {
        if( handle != -1L ) {
            Long key ;
            Iterator it = _mmapManager.keySet().iterator() ;
            while( it.hasNext() ) {
                key = (Long)it.next() ;
                _mmapManager.get( key ).clear() ;
            }
            NativeIO.fsync( handle ) ;
            NativeIO.close( handle ) ;
        }
        handle = -1L ;
        name = null ;
    }
    
    /**
     * ファイルキャッシュ強制出力.
     * @exception Exception 例外.
     */
    public void fsync() throws Exception {
        if( handle != -1L ) {
            // mmapデータを更新.
            Long key ;
            Iterator it = _mmapManager.keySet().iterator() ;
            while( it.hasNext() ) {
                key = (Long)it.next() ;
                _mmapManager.get( key ).flush() ;
            }
            // fsync更新.
            if( NativeIO.fsync( handle ) != 0 ) {
                throw new IOException( "fsync処理に失敗" ) ;
            }
        }
    }
    
    /**
     * メモリ情報を生成.
     * @param fastMode [true]の場合、引数チェックしないMmapBufferで生成します.
     * @param length 対象のメモリサイズを設定します.
     * @return NativeBuffer メモリオブジェクトが返されます.
     */
    public static final NativeBuffer createMemory( boolean fastMode,int length )  {
        if( fastMode ) {
            return new FastMemoryBuffer( length ) ;
        }
        return new MemoryBuffer( length ) ;
    }
    
    /**
     * mmap情報を生成.
     * @param offset mmap開始位置を設定します.
     * @param length mmap設定長を設定します.
     * @return MmapBuffer mmapオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public MmapBuffer createMmap( long offset,int length )
        throws Exception {
        return createMmap( false,offset,length ) ;
    }
    
    /**
     * mmap情報を生成.
     * @param fastMode [true]の場合、引数チェックしないMmapBufferで生成します.
     * @param offset mmap開始位置を設定します.
     * @param length mmap設定長を設定します.
     * @return MmapBuffer mmapオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public MmapBuffer createMmap( boolean fastMode,long offset,int length )
        throws Exception {
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        if( fastMode ) {
            return new FastMmapBufferImpl( this,offset,length ) ;
        }
        else {
            return new MmapBufferImpl( this,offset,length ) ;
        }
    }
    
    /**
     * 読み込み処理.
     * @param buffer 読み込み先NativeBufferを設定します.
     * @param offset 読み込みオフセット値を設定します.
     * @param length 読み込みデータ長を設定します.
     * @return int 読み込みデータ長が返されます.
     * @exception Exception 例外.
     */
    public int read( NativeBuffer buffer,int offset,int length )
        throws Exception {
        return read( buffer,-1L,offset,length ) ;
    }
    
    /**
     * 読み込み処理.
     * @param buffer 読み込み先NativeBufferを設定します.
     * @param seek シーク位置を設定します.
     * @param offset 読み込みオフセット値を設定します.
     * @param length 読み込みデータ長を設定します.
     * @return int 読み込みデータ長が返されます.
     * @exception Exception 例外.
     */
    public int read( NativeBuffer buffer,long seek,int offset,int length )
        throws Exception {
        int bufferLength ;
        if( buffer == null || buffer.isClear() || ( bufferLength = buffer.getLength() ) <= 0 || offset < -0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        length = ( length <= 0 ) ? bufferLength - offset : length ;
        if( length <= 0 || length > bufferLength ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        return NativeIO.read( handle,seek,buffer.getAddress(),offset,length ) ;
    }
    
    /**
     * 書込み処理.
     * @param buffer 書き込み先NativeBufferを設定します.
     * @param offset 書込みオフセット値を設定します.
     * @param length 書込みデータ長を設定します.
     * @exception Exception 例外.
     */
    public void write( NativeBuffer buffer,int offset,int length )
        throws Exception {
        write( buffer,-1L,offset,length ) ;
    }
    
    /**
     * 書込み処理.
     * @param buffer 書き込み先NativeBufferを設定します.
     * @param seek シーク位置を設定します.
     * @param offset 書込みオフセット値を設定します.
     * @param length 書込みデータ長を設定します.
     * @exception Exception 例外.
     */
    public void write( NativeBuffer buffer,long seek,int offset,int length )
        throws Exception {
        int bufferLength ;
        if( buffer == null || buffer.isClear() || ( bufferLength = buffer.getLength() ) <= 0 || offset < -0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        length = ( length <= 0 ) ? bufferLength - offset : length ;
        if( length <= 0 || length > bufferLength ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( NativeIO.write( handle,seek,buffer.getAddress(),offset,length ) <= -1 ) {
            throw new IOException( "書込み処理に失敗しました" ) ;
        }
    }
    
    /**
     * 現在のシーク位置を取得.
     * @return long 現在のシーク位置が返されます.
     * @exception Exception 例外.
     */
    public long getSeek() throws Exception {
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        return NativeIO.getSeek( handle ) ;
    }
    
    /**
     * 現在のファイルサイズを取得.
     * @return long ファイルサイズが返されます.
     * @exception Exception 例外.
     */
    public long length() throws Exception {
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        return NativeIO.getLength( handle ) ;
    }
    
    /**
     * ファイルサイズを設定.
     * @param length ファイルサイズを設定します.
     * @exception Exception 例外.
     */
    public void length( long length ) throws Exception {
        if( length <= -1L ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( handle == -1L ) {
            throw new IOException( "オブジェクトは既にクローズしています" ) ;
        }
        NativeIO.setLength( handle,length ) ;
    }
    
    /**
     * オープンファイル名を取得.
     * @return String オープンファイル名が返されます.
     */
    public String getName() {
        return name ;
    }
    
    /**
     * ファイルオプションを取得.
     * @return int ファイルオプションが返されます.
     */
    public int getOption() {
        return opt ;
    }
    
    /**
     * ファイルがオープン中かチェック.
     * @return boolean [true]の場合、オープンしています.
     */
    public boolean isOpen() {
        return ( handle != -1L ) ;
    }
    
    /**
     * ページファイルサイズを取得.
     * @return int ページファイルサイズが返されます.
     */
    public static final int getPageFileLength() {
        return NativeIO.pageFileLength() ;
    }
    
    /**
     * ページファイルシフト値を取得.
     * @return int ページファイルシフト値が返されます.
     */
    public static final int getPageFileShift() {
        return NativeIO.pageFileShift() ;
    }
}

