package org.maachang.rawio.async ;

import java.io.IOException;

import org.maachang.rawio.Baseio;
import org.maachang.rawio.Rawio;
import org.maachang.rawio.RawioInstance;

/**
 * 非同期書込みI/O.
 *  
 * @version 2008/06/12
 * @author  masahito suzuki
 * @since   Rawio 1.00
 */
class ASyncio implements Baseio {
    
    /**
     * Factoryオブジェクト.
     */
    private ASyncFactory factory = null ;
    
    /**
     * 非同期書込み用キュー.
     */
    private ASyncQueue queue = null ;
    
    /**
     * 最大非同期書込みバッファ数.
     */
    private int maxAsyncLength = -1 ;
    
    /**
     * RawI/Oオブジェクト.
     */
    private Rawio rawio = null ;
    
    /**
     * ブロックロックオブジェクト.
     */
    private ASyncWriteLock lock = null ;
    
    /**
     * セクタサイズ.
     */
    private int sectorSize = 0 ;
    
    /**
     * コンストラクタ.
     */
    private ASyncio() {
        
    }
    
    /**
     * コンストラクタ.
     * @param mode 対象のモードを設定します.
     * @param factory 対象のFactoryオブジェクトを設定します.
     * @param queue 対象のQueueオブジェクトを設定します.
     * @param maxAsyncLength 最大非同期書込みバッファ数を設定します.
     * @param name オープンファイル名を設定します.
     * @exception Exception 例外.
     */
    public ASyncio( long mode,ASyncFactory factory,ASyncQueue queue,int maxAsyncLength,String name )
        throws Exception {
        if( factory.isStop() == true ) {
            throw new IOException( "Factoryオブジェクトは既に停止しています" ) ;
        }
        Rawio rw = null ;
        if( mode == ASyncFactory.MODE_AUTO ) {
            rw = ( Rawio )RawioInstance.open( name ) ;
        }
        else if( mode == ASyncFactory.MODE_DIRECT ) {
            rw = ( Rawio )RawioInstance.openDirect( name ) ;
        }
        else if( mode == ASyncFactory.MODE_EMULATOIN ) {
            rw = ( Rawio )RawioInstance.openEmulation( name ) ;
        }
        else {
            rw = ( Rawio )RawioInstance.open( name ) ;
        }
        ASyncWriteLock lk = new ASyncWriteLock( rw.length() ) ;
        this.factory = factory ;
        this.queue = queue ;
        this.maxAsyncLength = maxAsyncLength ;
        this.rawio = rw ;
        this.lock = lk ;
        this.sectorSize = rw.getSector() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    protected synchronized void destroy() {
        if( rawio != null ) {
            rawio.destroy() ;
        }
        if( lock != null ) {
            lock.destroy() ;
        }
        rawio = null ;
        lock = null ;
        queue = null ;
        factory = null ;
        maxAsyncLength = -1 ;
        sectorSize = 0 ;
    }
    
    /**
     * データの更新.
     * @exception Exception 例外.
     */
    public synchronized void flush() throws Exception {
        if( rawio != null ) {
            rawio.flush() ;
        }
    }
    
    /**
     * 容量を増やす.
     * @param size 追加する容量を設定します.
     * @exception Exception 例外.
     */
    public synchronized void expansion( int size ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( size <= 0 ) {
            return ;
        }
        rawio.expansion( size ) ;
        lock.expansion( rawio.length() ) ;
    }
    
    /**
     * セクタサイズを取得.
     * @return int セクタサイズが返されます.
     */
    public synchronized int getSector() {
        if( isUse() == false ) {
            return -1 ;
        }
        return sectorSize ;
    }
    
    /**
     * オープンファイル名を取得.
     * @return String オープンファイル名が返されます.
     */
    public synchronized String getName() {
        if( isUse() == false ) {
            return null ;
        }
        return rawio.getName() ;
    }
    
    /**
     * ファイルサイズを取得.
     * @return int ファイルサイズが返されます.<BR>
     *             単位は、セクタです.
     */
    public synchronized int length() {
        if( isUse() == false ) {
            return -1 ;
        }
        return rawio.length() ;
    }
    
    /**
     * ファイルを読み込む.
     * @return out 読み込まれたバイナリが返されます.
     * @param no 読み込み項番を設定します.
     * @exception Exception 例外.
     */
    public byte[] read( int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        int sec ;
        Rawio raw ;
        synchronized( this ) {
            sec = sectorSize ;
            raw = rawio ;
        }
        if( no < 0 || no > raw.length() ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        byte[] ret = new byte[ sec ] ;
        waitToRead( ret,no ) ;
        return ret ;
    }
    
    /**
     * ファイルを読み込む.
     * @return out 読み込まれたバイナリが返されます.
     * @param no 読み込み項番を設定します.
     * @exception Exception 例外.
     */
    public void read( byte[] out,int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( out == null || out.length != sectorSize ||
            no < 0 || no >= rawio.length() ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        waitToRead( out,no ) ;
    }
    
    /**
     * ファイルの書込み.
     * @param in 書込み対象のバイナリを設定します.
     * @param no 書込み項番を設定します.
     * @exceptino Exception 例外.
     */
    public void write( byte[] in,int no ) throws Exception {
        write( false,in,no ) ;
    }
    
    /**
     * ファイルの書込み.
     * @param mode [true]の場合、書込みバイナリをそのまま利用して処理します.<BR>
     *             ただし、この場合は、書込みバイナリ長はセクタ数と同一でないといけません.
     * @param in 書込み対象のバイナリを設定します.
     * @param no 書込み項番を設定します.
     * @exception Exception 例外.
     */
    public void write( boolean mode,byte[] in,int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( in == null || no < 0 || no >= rawio.length() ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int max ;
        int sec ;
        Rawio raw ;
        ASyncWriteLock lk ;
        ASyncQueue q ;
        synchronized( this ) {
            max = maxAsyncLength ;
            sec = sectorSize ;
            raw = rawio ;
            lk = lock ;
            q = queue ;
        }
        byte[] b ;
        if( mode == true ) {
            if( in.length != sec ) {
                throw new IOException( "書込みバイナリ長は不正です" ) ;
            }
            b = in ;
        }
        else {
            b = new byte[ sec ] ;
            if( in.length > 0 ) {
                int len = in.length ;
                if( len >= sec ) {
                    len = sec ;
                }
                System.arraycopy( in,0,b,0,len ) ;
            }
        }
        boolean syncWriteFlag = false ;
        for( ;; ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            synchronized( this ) {
                if( lock.flag( no ) == false ) {
                    if( max >= queue.elementSize() ) {
                        lk.on( no ) ;
                        syncWriteFlag = false ;
                        break ;
                    }
                    else {
                        lk.on( no ) ;
                        syncWriteFlag = true ;
                        break ;
                    }
                }
            }
            Thread.sleep( 1L ) ;
        }
        if( syncWriteFlag == true ) {
            try {
                raw.write( b,no ) ;
            } catch( Exception e ) {
            } finally {
                lk.off( no ) ;
            }
        }
        else {
            q.append( new ASyncElement( raw,lk,b,no ) ) ;
        }
    }
    
    /**
     * オブジェクトタイプを取得.
     * @return int オブジェクトタイプが返されます.
     */
    public int getType() {
        return IO_TYPE_ASYNC ;
    }
    
    private synchronized boolean isUse() {
        return ( rawio != null && factory.isStop() == false ) ;
    }
    
    private void waitToRead( byte[] out,int no ) throws Exception {
        Rawio raw ;
        ASyncWriteLock lk ;
        synchronized( this ) {
            raw = rawio ;
            lk = lock ;
        }
        for( ;; ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            synchronized( this ) {
                if( lk.flag( no ) == false ) {
                    break ;
                }
            }
            Thread.sleep( 1L ) ;
        }
        raw.read( out,no ) ;
    }
}
