package org.maachang.jni.io ;

import java.io.IOException;

/**
 * 通常MmapBuffer.
 * 
 * @version 2010/06/04
 * @author  masahito suzuki
 * @since   SeabassNativeIO-1.0.0
 */
 
class MmapBufferImpl extends AbstractNativeBuffer implements MmapBuffer {
    protected long handle ;
    protected long firstAddress ;
    protected int memoryLength ;
    protected long offsetLength ;
    protected int opt ;
    protected NativeRandomIO io ;
    protected long seqNo ;
    
    private MmapBufferImpl() {
        
    }
    
    /**
     * コンストラクタ.
     * @param io mmap対象元のファイルオブジェクトを設定します.
     * @param offset mmap開始位置を設定します.
     * @param length mmap設定長を設定します.
     * @exception Exception 例外.
     */
    protected MmapBufferImpl( NativeRandomIO io,long offset,int length )
        throws Exception {
        if( !io.isOpen() ) {
            throw new IOException( "mmap参照先のファイルは既にクローズされています" ) ;
        }
        if( offset < 0L || length <= 0 ) {
            throw new IllegalArgumentException( "mmap指定範囲は範囲外です(off:" + offset + " len:" + length + ")" ) ;
        }
        int opt = ( io.getOption() == NativeRandomIO.OPT_READ ) ? OPT_READ : OPT_WRITE ;
        long srcOffset = offset ;
        int srcLength = length ;
        offset = MmapBufferImpl.getOffsetToPageSize( offset ) ;
        long addOffset = srcOffset - offset ;
        length = MmapBufferImpl.getLengthToPageSize( length + (int)addOffset ) ;
        long allLen = (offset + (long)length) ;
        if( NativeIO.getLength( io.handle ) < allLen ) {
            NativeIO.setLength( io.handle,allLen ) ;
        }
        int mmapLength = NativeIO.mmapLength( srcLength ) ;
        long[] out = new long[ 2 ] ;
        if( NativeIO.createMmap( out,io.handle,opt,offset,mmapLength ) != 0 ) {
            throw new IOException( "Mmapの生成に失敗しました(name:" + io.getName() + " off:" + offset + " len:" + length + ")" ) ;
        }
        long addr = out[ 0 ] ;
        super.address = addr + addOffset ;
        super.length = srcLength ;
        this.handle = out[ 1 ] ;
        this.firstAddress = addr ;
        this.memoryLength = mmapLength ;
        this.offsetLength = allLen ;
        this.opt = opt ;
        this.io = io ;
        this.seqNo = io._seq.get() ;
        io._mmapManager.put( this.seqNo,this ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        clear() ;
        io = null ;
    }
    
    /**
     * mmapクリア.
     */
    public void clear() {
        if( firstAddress != 0L ) {
            if( opt == OPT_WRITE ) {
                NativeIO.flushMmap( firstAddress,memoryLength ) ;
            }
            NativeIO.closeMmap( handle,firstAddress,memoryLength ) ;
            handle = -1L ;
            firstAddress = 0L ;
            memoryLength = -1 ;
            address = 0L ;
            length = 0 ;
            offsetLength = 0 ;
            io._mmapManager.remove( seqNo ) ;
            seqNo = -1L ;
        }
    }
    
    /**
     * mmap再割り当て.
     * @param offset mmap開始位置を設定します.
     * @param length mmap設定長を設定します.
     * @exception Exception 例外.
     */
    public void reCreate( long offset,int length )
        throws Exception {
        if( !io.isOpen() ) {
            throw new IOException( "mmap参照先のファイルは既にクローズされています" ) ;
        }
        if( offset < 0L || length <= 0 ) {
            throw new IllegalArgumentException( "mmap指定範囲は範囲外です(off:" + offset + " len:" + length + ")" ) ;
        }
        clear() ;
        long srcOffset = offset ;
        int srcLength = length ;
        offset = MmapBufferImpl.getOffsetToPageSize( offset ) ;
        long addOffset = srcOffset - offset ;
        length = MmapBufferImpl.getLengthToPageSize( length + (int)addOffset ) ;
        long allLen = (offset + (long)length) ;
        if( NativeIO.getLength( io.handle ) < allLen ) {
            NativeIO.setLength( io.handle,allLen ) ;
        }
        int mmapLength = NativeIO.mmapLength( srcLength ) ;
        long[] out = new long[ 2 ] ;
        if( NativeIO.createMmap( out,io.handle,opt,offset,mmapLength ) != 0 ) {
            throw new IOException( "Mmapの生成に失敗しました(name:" + io.getName() + " off:" + offset + " len:" + length + ")" ) ;
        }
        long addr = out[ 0 ] ;
        super.address = addr + addOffset ;
        super.length = srcLength ;
        this.handle = out[ 1 ] ;
        this.firstAddress = addr ;
        this.memoryLength = mmapLength ;
        this.offsetLength = allLen ;
        this.seqNo = this.io._seq.get() ;
        this.io._mmapManager.put( this.seqNo,this ) ;
    }
    
    /**
     * mmap領域を強制書き込み.
     * @exception Exception 例外.
     */
    public void flush()
        throws Exception {
        if( opt == OPT_READ ) {
            // 読み込み専用の場合は処理しない.
            return ;
        }
        if( address == 0L ) {
            throw new IllegalStateException( "既にクリアされています" ) ;
        }
        if( !io.isOpen() ) {
            throw new IOException( "mmap参照先のファイルは既にクローズされています" ) ;
        }
        if( NativeIO.flushMmap( firstAddress,memoryLength ) != 0 ) {
            throw new IOException( "mmap強制書き込みに失敗しました" ) ;
        }
    }
    
    /**
     * mmap対象元のファイルオブジェクトを取得.
     * @return io 対象のファイルオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public NativeRandomIO getFileObject()
        throws Exception {
        if( io == null ) {
            throw new IOException( "mmap参照先のファイルは既にクローズされています" ) ;
        }
        return io ;
    }
    
    /**
     * mmap対象元のファイルが閉じているかチェック.
     * @return boolean [true]の場合、ファイルは閉じています.
     */
    public boolean isFileClose() {
        return !io.isOpen() ;
    }
    
    /**
     * mmap Read/Writeモードを取得.
     * @return int mmap Read/Writeモードが返されます.
     */
    public int getOption() {
        return opt ;
    }
    
    /**
     * オフセット値+長さを取得.
     * @return long オフセット値+長さのアドレスが返されます.
     */
    public long getOffsetLength() {
        return offsetLength ;
    }
    
    /**
     * put/get系メソッド呼び出し時のチェック処理.
     * @param mode [true]の場合、書き込み処理です.
     */
    protected void check( boolean mode ) {
        if( !io.isOpen() ) {
            throw new IllegalStateException( "mmap参照先のファイルは既にクローズされています" ) ;
        }
        if( mode && opt == OPT_READ ) {
            throw new IllegalStateException( "mmapアクセス条件は、読み込み専用です" ) ;
        }
    }
    
    /** ページファイル情報 **/
    private static final int _pageSize ;
    private static final int _pageShift ;
    private static final int _pageMask ;
    private static final int _pageNMask ;
    static {
        _pageSize = NativeRandomIO.getPageFileLength() ;
        _pageShift = NativeRandomIO.getPageFileShift() ;
        _pageMask = _pageSize - 1 ;
        _pageNMask = ~_pageMask ;
    }
    
    /**
     * 指定サイズをページファイルサイズに変更.
     * @param size 指定サイズを設定します.
     * @return int 指定サイズをページファイルサイズに変換します.
     */
    protected static final int getLengthToPageSize( final int size ) {
        return (
            ( ( size & _pageNMask ) >> _pageShift ) +
            //( ( ( size & _pageMask ) != 0 ) ? 1 : 0 )
            ( ( ( ( size & _pageMask ) - ( ( size & _pageMask ) << 1 ) ) >> 31 ) & 0x00000001 )
        ) * _pageSize ;
    }
    
    /**
     * オフセット開始位置を取得.
     * @param offset 対象のオフセット値を設定します.
     * @return long ページファイルサイズに合わされたオフセット値が返されます.
     */
    protected static final long getOffsetToPageSize( final long offset ) {
        return ( ( offset & (~((long)_pageMask) ) ) >> (long)_pageShift ) * (long)_pageSize ;
    }

}

