package org.maachang.dbm.engine ;

import java.io.IOException;

import org.maachang.rawio.Baseio;
import org.maachang.rawio.Rawio;
import org.maachang.rawio.RawioInstance;
import org.maachang.rawio.mapping.Mappingio;
import org.maachang.util.ConvertParam;
import org.maachang.util.FileUtil;

/**
 * １ファイルでのセクタ管理を行う.
 * 
 * @version 2008/06/05
 * @author masahito suzuki
 * @since MaachangDBM 1.12
 */
class M2OneFileSector {
    
    /**
     * 1ファイルでの最大セクタ数.
     */
    public static final int MAX_ONE_FILE = MDbmDefine.MAX_ONE_FILE_SECTOR ;
    
    /**
     * セクタ拡張子.
     */
    public static final String SECTOR_PLUS = ".sct2" ;
    
    /**
     * セクタオフセット.
     */
    private static final int SECTOR_OFFSET = MDbmDefine.SECTOR_HEADER ;
    
    /**
     * 最初のファイルセクタ数.
     */
    private static final int FIRST_SECTOR = 8192 ;
    
    /**
     * 空きフラグ管理用.
     */
    private M2RawFlag flags = null ;
    
    /**
     * セクタ書込みオブジェクト.
     */
    private Baseio io = null ;
    
    /**
     * ファイルNo.
     */
    private int fileNo = -1 ;
    
    /**
     * セクタ開始位置.
     */
    private int startSectorPos = -1 ;
    
    /**
     * 現在ファイルサイズ.
     */
    private int nowFileSize = 0 ;
    
    /**
     * 付加条件長.
     */
    private int nextAdd = 0 ;
    
    /**
     * コンストラクタ.
     */
    private M2OneFileSector() {
        
    }
    
    /**
     * コンストラクタ.
     * @param fileNo 対象のファイルNoを設定します.
     * @param dir 対象のディレクトリ名を設定します.
     * @param size 管理セクタ数を設定します.
     * @exception Exception 例外.
     */
    public M2OneFileSector( int fileNo,String dir,int size ) throws Exception {
        if( fileNo <= -1 || dir == null || ( dir = dir.trim() ).length() <= 0 ||
            size <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( FileUtil.isDirExists( dir ) == false ) {
            throw new IllegalArgumentException( "指定ディレクトリ[" + dir + "]は存在しません" ) ;
        }
        if( size >= MAX_ONE_FILE ) {
            size = MAX_ONE_FILE ;
        }
        if( dir.endsWith( "/" ) == false && dir.endsWith( "\\" ) == false ) {
            dir += "/" ;
        }
        String name = new StringBuilder().append( dir ).append( fileNo ).append( SECTOR_PLUS ).toString() ;
        boolean newFile = FileUtil.isFileExists( name ) == false ;
        Baseio baseio = RawioInstance.open( true,name ) ;
        int flagSize = M2RawFlag.convertRect( baseio.getSector(),size ) ;
        int bodySize = convertSectorSize( baseio.getSector(),baseio.getSector() * FIRST_SECTOR ) ;
        if( baseio.length() == 0 ) {
            baseio.expansion( flagSize + bodySize ) ;
        }
        M2RawFlag flg = new M2RawFlag( newFile,baseio,0,size,flagSize ) ;
        this.flags = flg ;
        this.io = baseio ;
        this.fileNo = fileNo ;
        this.startSectorPos = flagSize ;
        this.nowFileSize = baseio.length() ;
        this.nextAdd = nextAddSize( this.nowFileSize ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public synchronized void destroy() {
        if( flags != null ) {
            flags.destroy() ;
        }
        if( io != null ) {
            try {
                ( ( Mappingio )io ).flush() ;
            } catch( Exception e ) {
            }
            ( ( Rawio )( ( Mappingio )io ).getBaseio() ).destroy() ;
        }
        flags = null ;
        io = null ;
        fileNo = -1 ;
        startSectorPos = -1 ;
        nowFileSize = -1 ;
        nextAdd = -1 ;
    }
    
    /**
     * １つのセクタを予約.
     * @return int [-1]の場合、予約できませんでした.
     * @exception Exception 例外.
     */
    public int getPos() throws Exception {
        return flags.usePosBySet( 0 ) ;
    }
    
    /**
     * １つのセクタを削除.
     * @param no 削除対象のセクタを設定します.
     * @exception Exception 例外.
     */
    public void removePos( int no ) throws Exception {
        flags.removePos( no ) ;
    }
    
    /**
     * 指定内容のデータを読み込む.
     * @param outHeader 対象のヘッダを設定します.
     * @param out 読み込まれたデータが格納されます.
     * @param no 読み込み位置を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @exception Exception 例外.
     */
    public void read( M2SectorHeader outHeader,byte[] out,int no ) throws Exception {
        read( outHeader,out,no,0 ) ;
    }
    
    /**
     * 指定内容のデータを読み込む.
     * @param header 対象のヘッダを設定します.
     * @param out 読み込まれたデータが格納されます.
     * @param no 読み込み位置を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @exception Exception 例外.
     */
    public void read( M2SectorHeader outHeader,byte[] out,int no,int offset ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        outHeader.clear() ;
        if( no >= 0 ) {
            no = no + startSectorPos ;
            byte[] b = io.read( no ) ;
            outHeader.setSectorType( ConvertParam.convertInt( 0,b ) ) ;
            outHeader.setNextNo( ConvertParam.convertInt( 4,b ) ) ;
            outHeader.setNextFileNo( ConvertParam.convertInt( 8,b ) ) ;
            outHeader.setLength( ConvertParam.convertInt( 12,b ) ) ;
            if( outHeader.getLength() > 0 ) {
                System.arraycopy( b,SECTOR_OFFSET,out,offset,outHeader.getLength() ) ;
            }
        }
    }
    
    /**
     * 指定内容の書き込み処理を行う.
     * @param header 書き込みセクタヘッダを設定します.
     * @param in 書き込み内容を設定します.
     * @param no 書き込み位置を設定します.
     * @exception Exception 例外.
     */
    public void write( M2SectorHeader header,byte[] in,int no ) throws Exception {
        write( header,in,no,0 ) ;
    }
    
    /**
     * 指定内容の書き込み処理を行う.
     * @param header 書き込みセクタヘッダを設定します.
     * @param in 書き込み内容を設定します.
     * @param no 書き込み位置を設定します.
     * @param offset 対象のオフセット値を設定します.
     * @exception Exception 例外.
     */
    public void write( M2SectorHeader header,byte[] in,int no,int offset ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( no >= 0 ) {
            plus( no ) ;
            int sectorLen = io.getSector() ;
            no = no + startSectorPos ;
            byte[] b = new byte[ sectorLen ] ;
            ConvertParam.convertInt( b,0,header.getSectorType() ) ;
            ConvertParam.convertInt( b,4,header.getNextNo() ) ;
            ConvertParam.convertInt( b,8,header.getNextFileNo() ) ;
            ConvertParam.convertInt( b,12,header.getLength() ) ;
            System.arraycopy( in,offset,b,SECTOR_OFFSET,header.getLength() ) ;
            io.write( true,b,no ) ;
        }
    }
    
    /**
     * ファイル名を取得.
     * @return String ファイル名が返されます.
     */
    public String getName() {
        return io.getName() ;
    }
    
    /**
     * 対象のセクタ長を取得.
     * @return int セクタ長が返されます.
     */
    public int getSector() {
        return io.getSector() ;
    }
    
    /**
     * ファイル項番を取得.
     * @return int ファイル項番が返されます.
     */
    public synchronized int getFileNo() {
        return fileNo ;
    }
    
    /**
     * 現在有効件数を取得.
     * @return int 有効セクタ件数が返されます.
     * @exception Exception 例外.
     */
    public int getSize() throws Exception {
        return flags.size() ;
    }
    
    /**
     * 最大件数を取得.
     * @return int 最大件数が返されます.
     * @exception Exception 例外.
     */
    public int getMax() throws Exception {
        return flags.maxSize() ;
    }
    
    /**
     * 次に増加させるサイズを取得.
     */
    private int nextAddSize( int now ) {
        return now - startSectorPos ;
    }
    
    /**
     * ファイルサイズ付与条件の場合、任意のサイズ分増やす.
     */
    private void plus( int no ) throws Exception {
        if( nextAdd >= flags.maxSize() ) {
            return ;
        }
        if( no >= nextAdd ) {
            nextAdd = ( int )( ( double )nextAdd * 1.5f ) ;
            if( nextAdd >= flags.maxSize() ) {
                nextAdd = flags.maxSize() ;
            }
            int x = nowFileSize - startSectorPos ;
            x = nextAdd - x ;
            io.expansion( x ) ;
            nowFileSize = io.length() ;
        }
    }
    
    /**
     * オブジェクトチェック.
     */
    private synchronized boolean isUse() {
        return io != null ;
    }
    
    /**
     * 指定サイズをセクタ長に変換.
     */
    private static final int convertSectorSize( int sector,int size ) {
        int ret = size / sector ;
        if( size % sector != 0 ) {
            ret ++ ;
        }
        return ret ;
    }
}
