package org.maachang.dbm.engine ;

import java.io.IOException;

import org.maachang.rawio.RawioInstance;
import org.maachang.util.ArrayBinary;
import org.maachang.util.FileUtil;

/**
 * セクタ管理を行うオブジェクト.
 * 
 * @version 2008/06/05
 * @author masahito suzuki
 * @since MaachangDBM 1.12
 */
public class M2Sector {
    
    /**
     * セクタサイズ.
     */
    public static final int SECTOR_LENGTH = MDbmEnv.ONE_SECTOR_BY_SECTOR_SIZE ;
    
    /**
     * セクタ管理群.
     */
    private M2SectorList manager = null ;
    
    /**
     * 対象ディレクトリ.
     */
    private String dir = null ;
    
    /**
     * 1Block長.
     */
    private int blockLength = -1 ;
    
    /**
     * コンストラクタ.
     */
    private M2Sector() {
        
    }
    
    /**
     * コンストラクタ.
     * @param dir 対象のディレクトリを設定します.
     * @exception Exception 例外.
     */
    public M2Sector( String dir ) throws Exception {
        if( dir == null || ( dir = dir.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        if( FileUtil.isDirExists( dir ) == false ) {
            throw new IOException( "指定ディレクトリ(" + dir + ")は存在しません" ) ;
        }
        this.blockLength = RawioInstance.getSector( dir ) - MDbmDefine.SECTOR_HEADER ;
        this.dir = dir ;
        this.manager = load( dir ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクトを破棄.
     */
    public void destroy() {
        if( manager != null ) {
            manager.destroy() ;
        }
        manager = null ;
        dir = null ;
    }
    
    /**
     * １つのセクタを読み込む.
     * @param out セクタデータを設定します.
     * @param no 対象のセクタNoを設定します.
     * @param fileNo 対象のファイルNoを設定します.
     * @exception Exception 例外.
     */
    public void readOne( M2SectorData out,int no,int fileNo )
        throws Exception {
        out.clear() ;
        if( manager.size() <= 0 ) {
            return ;
        }
        byte[] tmp = new byte[ blockLength ] ;
        M2OneFileSector one = manager.get( fileNo ) ;
        if( one == null ) {
            throw new IOException( "指定ファイルNo("+fileNo+")は存在しません" ) ;
        }
        one.read( out,tmp,no ) ;
        if( out.getLength() <= -1 ) {
            out.clear() ;
        }
        else {
            out.setData( tmp ) ;
        }
    }
    
    /**
     * 指定バイナリを読み込む.
     * @param no 対象のセクタNoを設定します.
     * @param fileNo 対象のファイルNoを設定します.
     * @return byte[] 対象のバイナリ情報が返されます.
     * @exception Exception 例外.
     */
    public byte[] readAll( int no,int fileNo ) throws Exception {
        if( manager.size() <= 0 ) {
            return null ;
        }
        byte[] tmp = new byte[ blockLength ] ;
        M2SectorHeader header = new M2SectorHeader() ;
        ArrayBinary buf = new ArrayBinary( blockLength ) ;
        for( ;; ) {
            if( no <= -1 || fileNo <= -1 ) {
                break ;
            }
            M2OneFileSector one = manager.get( fileNo ) ;
            if( one == null ) {
                throw new IOException( "指定ファイルNo("+fileNo+")は存在しません" ) ;
            }
            one.read( header,tmp,no ) ;
            if( header.getLength() <= -1 ) {
                break ;
            }
            buf.write( tmp,header.getLength() ) ;
            no = header.getNextNo() ;
            fileNo = header.getNextFileNo() ;
        }
        if( buf.length() <= 0 ) {
            return null ;
        }
        return buf.getBinary() ;
    }
    
    /**
     * １つのセクタを書き込む.
     * @param data 対象のセクタデータを設定します.
     * @param no 対象のセクタNoを設定します.
     * @param fileNo 対象のファイルNoを設定します.
     * @exception Exception 例外.
     */
    public void writeOne( M2SectorData data,int no,int fileNo ) throws Exception {
        if( data.getLength() <= 0 ) {
            return ;
        }
        if( data.getNextNo() <= -1 || data.getNextFileNo() <= -1 ) {
            data.setNextNo( -1 ) ;
            data.setNextFileNo( -1 ) ;
        }
        M2OneFileSector one = manager.get( fileNo ) ;
        if( one == null ) {
            throw new IOException( "指定ファイルNo("+fileNo+")は存在しません" ) ;
        }
        one.write( data,data.getData(),no ) ;
    }
    
    /**
     * 指定バイナリを書き込む.
     * @param sectorType 対象のセクタタイプを設定します.
     * @param binary 対象のバイナリを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param length 対象の長さを設定します.
     * @return long 先頭のポジション[low:ポジション high:ファイルNo]が返されます.
     * @exception Exception 例外.
     */
    public long writeAll( int sectorType,byte[] binary,int offset,int length )
        throws Exception {
        if( offset <= 0 ) {
            offset = 0 ;
            if( length <= 0 ) {
                length = binary.length ;
            }
        }
        else if( length <= 0 ) {
            length = binary.length - offset ;
        }
        M2SectorHeader header = new M2SectorHeader() ;
        header.setSectorType( sectorType ) ;
        long ret = -1L ;
        int addLen = 0 ;
        int p = -1 ;
        M2OneFileSector one = null ;
        for( int cnt = 0 ;; cnt ++ ) {
            int len = ( length >= blockLength + addLen ) ?
                blockLength : length - addLen ;
            if( cnt == 0 ) {
                synchronized( this ) {
                    one = getFileSector( one ) ;
                    p = one.getPos() ;
                }
                ret = ( ( ( long )p & 0x00000000ffffffffL ) |
                    ( ( ( long )one.getFileNo() & 0x00000000ffffffffL ) << 32L ) ) ;
                if( len >= length ) {
                    header.setNextNo( -1 ) ;
                    header.setNextFileNo( -1 ) ;
                    header.setLength( len ) ;
                    one.write( header,binary,p,offset ) ;
                    break ;
                }
                else {
                    int pp ;
                    M2OneFileSector next ;
                    synchronized( this ) {
                        next = getFileSector( one ) ;
                        pp = next.getPos() ;
                    }
                    header.setNextNo( pp ) ;
                    header.setNextFileNo( next.getFileNo() ) ;
                    header.setLength( len ) ;
                    one.write( header,binary,p,offset ) ;
                    addLen += len ;
                    one = next ;
                    p = pp ;
                }
            }
            else {
                if( len + addLen >= length ) {
                    header.setNextNo( -1 ) ;
                    header.setNextFileNo( -1 ) ;
                    header.setLength( len ) ;
                    one.write( header,binary,p,addLen+offset ) ;
                    break ;
                }
                else {
                    int pp ;
                    M2OneFileSector next ;
                    synchronized( this ) {
                        next = getFileSector( one ) ;
                        pp = next.getPos() ;
                    }
                    header.setNextNo( pp ) ;
                    header.setNextFileNo( next.getFileNo() ) ;
                    header.setLength( len ) ;
                    one.write( header,binary,p,addLen+offset ) ;
                    addLen += len ;
                    one = next ;
                    p = pp ;
                }
            }
        }
        return ret ;
    }
    
    /**
     * 指定バイナリを削除.
     * @param no 対象のセクタNoを設定します.
     * @param fileNo 対象のファイルNoを設定します.
     * @exception Exception 例外.
     */
    public void removeAll( int no,int fileNo ) throws Exception {
        if( manager.size() <= 0 ) {
            return ;
        }
        byte[] tmp = new byte[ blockLength ] ;
        M2SectorHeader header = new M2SectorHeader() ;
        for( ;; ) {
            M2OneFileSector one = manager.get( fileNo ) ;
            if( one == null ) {
                throw new IOException( "指定ファイルNo("+fileNo+")は存在しません" ) ;
            }
            one.read( header,tmp,no ) ;
            one.removePos( no ) ;
            if( header.getNextNo() <= -1 || header.getNextFileNo() <= -1 ) {
                break ;
            }
            no = header.getNextNo() ;
            fileNo = header.getNextFileNo() ;
        }
    }
    
    /**
     * セクタファイル数を取得.
     * @return int セクタファイル数が返されます.
     */
    public synchronized int getSize() {
        return manager.size() ;
    }
    
    /**
     * Blockサイズを取得.
     * @return int Blockサイズが返されます.
     */
    public synchronized int getBlockSize() {
        return blockLength ;
    }
    
    /**
     * データローディング.
     */
    private static final M2SectorList load( String dir )
        throws Exception {
        String[] lst = FileUtil.getFileList( dir ) ;
        int len = lst.length ;
        M2SectorList ret = new M2SectorList() ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( lst[ i ] == null || lst[ i ].endsWith( M2OneFileSector.SECTOR_PLUS ) == false ) {
                continue ;
            }
            int fileNo = -1 ;
            try {
                fileNo = Integer.parseInt(
                    lst[ i ].substring(
                        0,lst[ i ].length() - M2OneFileSector.SECTOR_PLUS.length() ).trim() ) ;
            } catch( Exception e ) {
                fileNo = -1 ;
            }
            if( fileNo == -1 ) {
                continue ;
            }
            M2OneFileSector one = new M2OneFileSector( fileNo,dir,SECTOR_LENGTH ) ;
            ret.put( fileNo,one ) ;
        }
        return ret ;
    }
    
    /**
     * 空き条件が存在しない場合は、新しく作成して取得.
     */
    private synchronized M2OneFileSector getFileSector( M2OneFileSector o ) throws Exception {
        if( o == null || o.getMax() <= o.getSize() ) {
            if( ( o = getUseFileSector() ) == null ) {
                o = newUseFileSector() ;
            }
        }
        else {
            o = getUseFileSector() ;
        }
        return o ;
    }
    
    /**
     * 空き条件を取得.
     */
    private M2OneFileSector getUseFileSector() throws Exception {
        int len = manager.size() ;
        for( int i = 1 ; i < len ; i ++ ) {
            M2OneFileSector m = manager.get( i ) ;
            if( m != null && m.getMax() > m.getSize() ) {
                return m ;
            }
        }
        return null ;
    }
    
    /**
     * 新しい領域を生成.
     */
    private M2OneFileSector newUseFileSector() throws Exception {
        int no = manager.size() + 1 ;
        for( ;; ) {
            if( manager.get( no ) != null ) {
                no ++ ;
                continue ;
            }
            break ;
        }
        M2OneFileSector one = new M2OneFileSector( no,dir,SECTOR_LENGTH ) ;
        manager.put( no,one ) ;
        return one ;
    }
    
}
