package org.maachang.rawio ;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * JNI対応以外での、Rawio.
 * <BR>
 * このオブジェクトでは、実際にRaw(Direct)I/Oを行わない
 * アクセス処理です.
 *  
 * @version 2008/06/12
 * @author  masahito suzuki
 * @since   Rawio 1.00
 */
class EmulationRawIOImpl implements Rawio {
    
    /**
     * セクタアクセスサイズ.
     */
    private static final int ETC_SECTOR_SIZE = 512 ;
    
    /**
     * ランダムアクセスファイル.
     */
    private RandomAccessFile fp = null ;
    
    /**
     * ファイル名.
     */
    private String name = null ;
    
    /**
     * セクタに対する長さ.
     */
    private int sectorToFileLength = -1 ;
    
    /**
     * オープンフラグ.
     */
    private volatile boolean isOpenFlag = false ;
    
    /**
     * 読み込み同期.
     */
    private final Object readSync = new Object() ;
    
    /**
     * 書込み同期.
     */
    private final Object writeSync = new Object() ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private EmulationRawIOImpl() {
        
    }
    
    /**
     * コンストラクタ.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public EmulationRawIOImpl( String name ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        name = new File( name ).getCanonicalPath() ;
        RandomAccessFile fp = new RandomAccessFile( name,"rwd" ) ;
        long size = fp.length() ;
        int sectorToFileLength = ( int )( size / ( long )ETC_SECTOR_SIZE ) ;
        this.fp = fp ;
        this.sectorToFileLength = sectorToFileLength ;
        this.isOpenFlag = true ;
        this.name = name ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        if( isOpen() == true ) {
            synchronized( sync ) {
                isOpenFlag = false ;
            }
            if( fp != null ) {
                try {
                    fp.close() ;
                } catch( Exception e ) {
                }
            }
        }
        fp = null ;
        sectorToFileLength = -1 ;
        name = null ;
    }
    
    /**
     * データの更新.
     * @exception Exception 例外.
     */
    public void flush() throws Exception {
        
    }
    
    /**
     * 容量を増やす.
     * @param size 追加する容量を設定します.
     * @exception Exception 例外.
     */
    public void expansion( int size ) throws Exception {
        if( isOpen() == false ) {
            throw new IOException( "ファイルは既に閉じています" ) ;
        }
        if( size <= 0 ) {
            return ;
        }
        int len = length() + size ;
        setLength( len ) ;
    }
    
    /**
     * セクタサイズを取得.
     * @return int セクタサイズが返されます.
     */
    public int getSector() {
        if( isOpen() ) {
            return ETC_SECTOR_SIZE ;
        }
        return -1 ;
    }
    
    /**
     * オープンファイル名を取得.
     * @return String オープンファイル名が返されます.
     */
    public String getName() {
        if( isOpen() ) {
            return name ;
        }
        return null ;
    }
    
    /**
     * ファイルサイズを取得.
     * @return int ファイルサイズが返されます.<BR>
     *             単位は、セクタです.
     */
    public int length() {
        if( isOpen() ) {
            return sectorToFileLength ;
        }
        return -1 ;
    }
    
    /**
     * ファイルサイズを設定.
     * @param size 対象のファイルサイズを設定します.<BR>
     *             単位は、セクタ単位で設定します.
     * @exception Exception 例外.
     */
    public void setLength( int size ) throws Exception {
        if( isOpen() ) {
            if( size <= -1 ) {
                size = 0 ;
            }
            long fileLan = ( long )ETC_SECTOR_SIZE * ( long )size ;
            fp.setLength( fileLan ) ;
            this.sectorToFileLength = size ;
        }
        else {
            throw new IOException( "ファイルは既に閉じています" ) ;
        }
    }
    
    /**
     * ファイルを読み込む.
     * @return out 読み込まれたバイナリが返されます.
     * @param no 読み込み項番を設定します.
     * @exception Exception 例外.
     */
    public byte[] read( int no ) throws Exception {
        byte[] ret = new byte[ ETC_SECTOR_SIZE ] ;
        read( ret,no ) ;
        return ret ;
    }
    
    /**
     * ファイルを読み込む.
     * @return out 読み込まれたバイナリが返されます.
     * @param no 読み込み項番を設定します.
     * @exception Exception 例外.
     */
    public void read( byte[] out,int no ) throws Exception {
        if( isOpen() == false ) {
            throw new IOException( "ファイルは既に閉じています" ) ;
        }
        if( out == null || out.length != ETC_SECTOR_SIZE ) {
            throw new IllegalArgumentException( "読み込み対象バイナリ長は不正です" ) ;
        }
        if( no <= -1 || no >= sectorToFileLength ) {
            throw new IllegalArgumentException( "指定項番[" + no + "]はファイルサイズ外です" ) ;
        }
        synchronized( readSync ) {
            fp.seek( ( long )no * ( long )ETC_SECTOR_SIZE ) ;
            fp.read( out ) ;
        }
    }
    
    /**
     * ファイルの書込み.
     * @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( isOpen() == false ) {
            throw new IOException( "ファイルは既に閉じています" ) ;
        }
        if( in == null ) {
            throw new IllegalArgumentException( "読み込み対象バイナリ長は不正です" ) ;
        }
        if( no <= -1 || no >= sectorToFileLength ) {
            throw new IllegalArgumentException( "指定項番[" + no + "]はファイルサイズ外です" ) ;
        }
        byte[] b = null ;
        if( in.length != ETC_SECTOR_SIZE ) {
            if( mode == true ) {
                throw new IOException( "書込みバイナリ長は不正です" ) ;
            }
            int len = in.length ;
            if( len > ETC_SECTOR_SIZE ) {
                len = ETC_SECTOR_SIZE ;
            }
            b = new byte[ ETC_SECTOR_SIZE ] ;
            System.arraycopy( in,0,b,0,len ) ;
        }
        else {
            b = in ;
        }
        synchronized( writeSync ) {
            fp.seek( ( long )no * ( long )ETC_SECTOR_SIZE ) ;
            fp.write( in ) ;
        }
    }
    
    /**
     * オブジェクトタイプを取得.
     * @return int オブジェクトタイプが返されます.
     */
    public int getType() {
        return IO_TYPE_BASE ;
    }
    
    private synchronized boolean isOpen() {
        return isOpenFlag ;
    }
}
