package org.maachang.rawio.mapping ;

import java.io.IOException;
import java.util.Iterator;

import org.maachang.rawio.Baseio;

/**
 * MappingI/O実装.
 *  
 * @version 2008/06/12
 * @author  masahito suzuki
 * @since   Rawio 1.00
 */
class MappingioImpl implements Mappingio {
    
    /**
     * Flush長.
     */
    protected static final int UPDATE_BY_FLUSH_LENGTH = 32 ;
    
    /**
     * Baseio.
     */
    private Baseio baseio = null ;
    
    /**
     * セクタサイズ.
     */
    private int sectorSize = -1 ;
    
    /**
     * Mapping管理.
     */
    private MappingManager manager = null ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private MappingioImpl() {
        
    }
    
    /**
     * コンストラクタ.
     * @param io 対象のBaseI/Oオブジェクトを設定します.
     * @exception Exception 例外.
     */
    public MappingioImpl( Baseio io ) throws Exception {
        if( io == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        this.baseio = io ;
        this.sectorSize = io.getSector() ;
        this.manager = new MappingManager() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    protected void destroy() {
        synchronized( sync ) {
            if( manager != null ) {
                manager.destroy() ;
            }
            baseio = null ;
            sectorSize = -1 ;
            manager = null ;
        }
    }
    
    /**
     * データの更新.
     * @exception Exception 例外.
     */
    public void flush() throws Exception {
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            int len = manager.length() ;
            for( int i = 0 ; i < len ; i ++ ) {
                MappingChild ch = manager.getArray( i ) ;
                if( ch.updateLength() > 0 ) {
                    int startPos = ch.getStartPos() ;
                    for ( Iterator<Integer> it = ch.iterator() ; it.hasNext() ; ) {
                        Integer no = it.next() ;
                        it.remove() ;
                        if( no != null ) {
                            int n = no.intValue() ;
                            byte[] b = ch.get( n ) ;
                            if( b != null ) {
                                try {
                                    baseio.write( b,startPos+n ) ;
                                } catch( Exception e ) {
                                    //e.printStackTrace() ;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    /**
     * 指定範囲のMappingを追加.
     * @param start Mapping開始位置を設定します.
     * @param length Mapping設定長を設定します.
     * @return Mapping操作オブジェクトが返されます.
     * @exception Exception 例外.
     */
    public MappingOp addMapping( int start,int length ) throws Exception {
        MappingOpImpl ret = null ;
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            if( start < 0 || length <= 0 || start + length > baseio.length() ) {
                throw new IllegalArgumentException( "指定範囲(start:" + start +
                    " length:" + length + ")は不正です" ) ;
            }
            MappingChild ch = manager.add( sectorSize,start,length ) ;
            int pos = start ;
            for( int i = 0 ; i < length ; i ++ ) {
                byte[] b = ch.get( i ) ;
                baseio.read( b,pos ) ;
                pos ++ ;
            }
            ret = new MappingOpImpl( this,ch,sync ) ;
        }
        return ret ;
    }
    
    /**
     * 指定位置がMaapingされているかチェック.
     * @param no ポジションを設定します.
     * @return boolean [true]の場合、Mappingされています.
     * @exception Exception 例外.
     */
    public boolean isMapping( int no ) throws Exception {
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            return manager.isMapping( no ) ;
        }
    }
    
    /**
     * 全マッピング長を取得.
     * @return int 全マッピング長が返されます.
     * @exception Exception 例外.
     */
    public int mappingSize() throws Exception {
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            return manager.mappingSize() ;
        }
    }
    
    /**
     * 元のBaseioを取得.
     * @return Baseio 元のBaseioが返されます.
     */
    public Baseio getBaseio() {
        synchronized( sync ) {
            if( isUse() == false ) {
                return null ;
            }
            return baseio ;
        }
    }
    
    /**
     * 容量を増やす.
     * @param size 追加する容量を設定します.
     * @exception Exception 例外.
     */
    public void expansion( int size ) throws Exception {
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            if( size <= 0 ) {
                return ;
            }
            baseio.expansion( size ) ;
        }
    }
    
    /**
     * セクタサイズを取得.
     * @return int セクタサイズが返されます.
     */
    public int getSector() {
        synchronized( sync ) {
            if( isUse() == false ) {
                return -1 ;
            }
            return sectorSize ;
        }
    }
    
    /**
     * オープンファイル名を取得.
     * @return String オープンファイル名が返されます.
     */
    public String getName() {
        synchronized( sync ) {
            if( isUse() == false ) {
                return null ;
            }
            return baseio.getName() ;
        }
    }
    
    /**
     * ファイルサイズを取得.
     * @return int ファイルサイズが返されます.<BR>
     *             単位は、セクタです.
     */
    public int length() {
        synchronized( sync ) {
            if( isUse() == false ) {
                return -1 ;
            }
            return baseio.length() ;
        }
    }
    
    /**
     * ファイルを読み込む.
     * @return out 読み込まれたバイナリが返されます.
     * @param no 読み込み項番を設定します.
     * @exception Exception 例外.
     */
    public byte[] read( int no ) throws Exception {
        byte[] ret = new byte[ sectorSize ] ;
        read( ret,no ) ;
        return ret ;
    }
    
    /**
     * ファイルを読み込む.
     * @param out 読み込まれたバイナリが返されます.
     * @param no 読み込み項番を設定します.
     * @exception Exception 例外.
     */
    public void read( byte[] out,int no ) throws Exception {
        if( out == null || out.length != sectorSize ||
            no < 0 || no >= baseio.length() ) {
            if( out == null ) {
                throw new IllegalArgumentException( "引数は不正です" ) ;
            }
            else if( no < 0 || no >= baseio.length() ) {
                throw new IllegalArgumentException( "引数は不正です["+ no + "/" + baseio.length() + "]" ) ;
            }
        }
        Baseio bio = null ;
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            bio = baseio ;
            MappingChild ch = manager.get( no ) ;
            if( ch != null ) {
                byte[] b = ch.get( no - ch.getStartPos() ) ;
                System.arraycopy( b,0,out,0,sectorSize ) ;
                return ;
            }
        }
        bio.read( out,no ) ;
    }
    
    /**
     * ファイルの書込み.
     * @param in 書込み対象のバイナリを設定します.
     * @param no 書込み項番を設定します.
     * @exception 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( in == null || no < 0 || no >= baseio.length() ) {
            if( in == null ) {
                throw new IllegalArgumentException( "引数は不正です" ) ;
            }
            else if( no < 0 || no >= baseio.length() ) {
                throw new IllegalArgumentException( "引数は不正です["+ no + "/" + baseio.length() + "]" ) ;
            }
        }
        Baseio bio = null ;
        synchronized( sync ) {
            if( isUse() == false ) {
                throw new IOException( "オブジェクトは既に破棄されています" ) ;
            }
            if( mode == true && in.length != sectorSize ) {
                throw new IOException( "書込みバイナリ長は不正です" ) ;
            }
            bio = baseio ;
            MappingChild ch = manager.get( no ) ;
            if( ch != null ) {
                int len = in.length ;
                if( len >= sectorSize ) {
                    len = sectorSize ;
                }
                int p = no - ch.getStartPos() ;
                byte[] b = ch.get( p ) ;
                System.arraycopy( in,0,b,0,len ) ;
                ch.updatePos( p ) ;
                updateFlush() ;
                return ;
            }
        }
        bio.write( in,no ) ;
    }
    
    /**
     * オブジェクトタイプを取得.
     * @return int オブジェクトタイプが返されます.
     */
    public int getType() {
        return IO_TYPE_MAPPING ;
    }
    
    /**
     * 更新Flushチェック.
     */
    protected void updateFlush() throws Exception {
        if( manager.getUpdateSize() >= UPDATE_BY_FLUSH_LENGTH ) {
            flush() ;
        }
    }
    
    /**
     * オブジェクトが有効かチェック.
     */
    protected boolean isUse() {
        return ( baseio != null ) ;
    }
}
