package org.maachang.rawio ;

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

/**
 * RawI/O基本オブジェクト.
 * <BR>
 * このオブジェクトでは、実際にRaw(Direct)I/Oを行います.
 *  
 * @version 2008/06/12
 * @author  masahito suzuki
 * @since   Rawio 1.00
 */
class DirectIOImpl implements Rawio {
    
    /**
     * ファイルポインタ.
     */
    private long fp = -1L ;
    
    /**
     * セクタ長.
     */
    private int sector = -1 ;
    
    /**
     * ファイル名.
     */
    private String name = null ;
    
    /**
     * ファイル長.
     */
    private long flen = -1L ;
    
    /**
     * セクタに対する長さ.
     */
    private int sectorToFileLength = -1 ;
    
    /**
     * オープンフラグ.
     */
    private volatile boolean isOpenFlag = false ;
    
    /**
     * 読み込み同期.
     */
    private final Object readSync = new Object() ;
    
    /**
     * 書込み同期.
     */
    private final Object writeSync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private DirectIOImpl() {
        
    }
    
    /**
     * コンストラクタ.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public DirectIOImpl( String name ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        name = new File( name ).getCanonicalPath() ;
        String path = getPath( name ) ;
        long fp = NativeRawIO.rawOpen( name.getBytes(),path.getBytes() ) ;
        if( fp == -1L ) {
            throw new IOException ( "ファイルオープンに失敗しました[" + name + "]" ) ;
        }
        int sector = NativeRawIO.getSectorByHandle( fp ) ;
        this.flen = NativeRawIO.getRawLength( fp ) ;
        this.fp = fp ;
        this.sector = sector ;
        this.name = name ;
        this.sectorToFileLength = ( int )( this.flen / ( long )this.sector ) ;
        this.isOpenFlag = true ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        if( isOpen() ) {
            synchronized( this ) {
                isOpenFlag = false ;
            }
            try {
                NativeRawIO.rawClose( fp ) ;
            } catch( Exception e ) {
            }
        }
        fp = -1L ;
        sector = -1 ;
        name = null ;
        flen = -1L ;
        sectorToFileLength = -1 ;
    }
    
    /**
     * データの更新.
     * @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 sector ;
        }
        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 )sector * ( long )size ;
            if( NativeRawIO.setRawLength( fp,fileLan ) <= -1 ) {
                throw new IOException( "ファイルサイズの設定に失敗しました[" + size + "]" ) ;
            }
            this.flen = 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[ sector ] ;
        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 != sector ) {
            throw new IllegalArgumentException( "読み込み対象バイナリ長は不正です" ) ;
        }
        if( no <= -1 || no >= sectorToFileLength ) {
            throw new IllegalArgumentException( "指定項番[" + no + "]はファイルサイズ外です" ) ;
        }
        int ret ;
        synchronized( readSync ) {
            ret = NativeRawIO.rawRead( fp,no,out ) ;
        }
        if( ret <= -1 ) {
            throw new IOException( "指定項番[" + 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( 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 != sector ) {
            if( mode == true ) {
                throw new IOException( "書込みバイナリ長は不正です" ) ;
            }
            int len = in.length ;
            if( len > sector ) {
                len = sector ;
            }
            b = new byte[ sector ] ;
            System.arraycopy( in,0,b,0,len ) ;
        }
        else {
            b = in ;
        }
        int ret ;
        synchronized( writeSync ) {
            ret = NativeRawIO.rawWrite( fp,no,b ) ;
        }
        if( ret <= -1 ) {
            throw new IOException( "指定項番[" + no + "]の書込みに失敗しました" ) ;
        }
    }
    
    /**
     * オブジェクトタイプを取得.
     * @return int オブジェクトタイプが返されます.
     */
    public int getType() {
        return IO_TYPE_BASE ;
    }
    
    /**
     * 指定ファイルのパス名を取得.
     */
    protected static final String getPath( String name ) throws Exception {
        name = new File( name ).getCanonicalPath() ;
        String path = name ;
        int p = path.indexOf( System.getProperty( "file.separator" ) ) ;
        if( p != -1 ) {
            path = path.substring( 0,p ) + System.getProperty( "file.separator" ) ;
        }
        return path ;
    }
    
    private synchronized boolean isOpen() {
        return isOpenFlag ;
    }
}
