#include "rawio.h"

// ファイルオープンパーミッション.
#define PERMISSION 0666

// セクタサイズ.
#define SECTOR_SIZE 512

///////////////////////////////////////////////////////////////////////////////
// prototype.
///////////////////////////////////////////////////////////////////////////////

// 基本I/O系.
LONG64 _GetPosition( int sector,int pos ) ;
int _GetSector( const char* path ) ;
int _FileRead( LPMHDL mh,char* buf,LONG64 pos,int length ) ;
int _FileWrite( LPMHDL mh,const char* buf,LONG64 pos,int length ) ;

// Mapping系.
void _Lock( LPMHDL mh ) ;
void _Unlock( LPMHDL mh ) ;
BOOL _GetPosRawIO( LPMHDL mh,LONG64 p ) ;
BOOL _GetFileLength( LPMHDL mh ) ;
BOOL _SetFileLenght( LPMHDL mh,LONG64 len ) ;
BOOL _CreateBuffer( LPMHDL out,int handle,int sector ) ;
void _DestroyBuffer( LPMHDL mh ) ;
BOOL _ClearWritePos( LPMHDL mh,int no ) ;
BOOL _DeleteOneBuffer( BOOL destroyFlag,LPMHDL mh,int no ) ;
BOOL _DeleteBuffer( LPMHDL mh ) ;
int _UseSpaceBuffer( LPMHDL mh ) ;
int _SearchMapping( LPMHDL mh,int addExp,LONG64 pos ) ;
BOOL _CreateMapping( LPMHDL mh,int mappingPos,LONG64 pos ) ;
int _ReadMapping( char* out,LPMHDL mh,LONG64 pos ) ;
int _WriteMapping( const char* in,LPMHDL mh,LONG64 pos ) ;

///////////////////////////////////////////////////////////////////////////////
// RawI/O内部実装.
///////////////////////////////////////////////////////////////////////////////

// クリティカルセクション(同期)開始.
void _Lock( LPMHDL mh ) {
    pthread_mutex_lock( &mh->section ) ;
}

// クリティカルセクション(同期)終了.
void _Unlock( LPMHDL mh ) {
    pthread_mutex_unlock( &mh->section ) ;
}

// 指定項番からSeekポジションを取得.
LONG64 _GetPosition( int sector,int pos ) {
    return ( LONG64 )( ( LONG64 )sector * ( LONG64 )pos ) ;
}

// セクタ長を取得.
int _GetSector( const char* path ) {
    return SECTOR_SIZE ;
}

// ファイル読み込み.
int _FileRead( LPMHDL mh,char* buf,LONG64 pos,int length ) {
    int ret ;
    ret = -1 ;
    ret = pread64( mh->handle,buf,length,pos ) ;
    if( ret <= -1 ) {
        return -1 ;
    }
    return ret ;
}

// ファイル書込み.
int _FileWrite( LPMHDL mh,const char* buf,LONG64 pos,int length ) {
    int ret ;
    ret = -1 ;
    ret = pwrite64( mh->handle,buf,length,pos ) ;
    if( ret <= -1 ) {
        return -1 ;
    }
    return ret ;
}

// ファイルサイズを取得.
BOOL _GetFileLength( LPMHDL mh ) {
    struct stat64 buf64;
    int ret;
    ret = FALSE ;
    _Lock( mh ) ;// lock.
    if( mh->handle > -1 ) {
        ret = fstat64( mh->handle,&buf64 );
        if( ret >= 0 ) {
            mh->fileLen = buf64.st_size ;
            ret = TRUE ;
        }
        else {
            mh->fileLen = 0 ;
            ret = FALSE ;
        }
    }
    _Unlock( mh ) ;// unlock.
    return ret;
}

// ファイルサイズをセット.
BOOL _SetFileLenght( LPMHDL mh,LONG64 len ) {
    BOOL ret = FALSE ;
    if( mh->handle > -1 ) {
        _Lock( mh ) ;// lock.
        ret = ftruncate64( mh->handle,len ) ;
        _Unlock( mh ) ;// unlock.
    }
    return ( ret == -1 ) ? FALSE : TRUE ;
}

// バッファ情報を生成.
BOOL _CreateBuffer( LPMHDL out,int handle,int sector ) {
    int i ;
    out->handle = handle ;
    out->sector = sector ;
    out->len = sector * MHDL_BUF ;
    pthread_mutex_init(&out->section,NULL);
    out->average = 0 ;
    out->maxNotRangeCount = 0 ;
    out->bufRange = ( LONG64 )( ( MHDL_BUF * BUF_SIZE * ( LONG64 )sector ) * BUFFER_RANGE ) ;
    for( i = 0 ; i < BUF_SIZE ; i ++ ) {
        memset( &out->bufs[ i ],0,sizeof( MBUF ) ) ;
    }
    // ファイル長を取得.
    if( _GetFileLength( out ) == FALSE ) {
        return FALSE ;
    }
    for( i = 0 ; i < BUF_SIZE ; i ++ ) {
        out->bufs[ i ].usePos = -1 ;
        out->bufs[ i ].useLen = -1 ;
        out->bufs[ i ].useWriteFlag = NOT_WRITE ;
        out->bufs[ i ].expire = 0 ;
        out->bufs[ i ].buf = ( char* )valloc( out->len ) ;
        _ClearWritePos( out,i ) ;
    }
    return TRUE ;
}

// すべてのバッファを破棄.
void _DestroyBuffer( LPMHDL mh ) {
    int i ;
    if( mh->handle != NULL ) {
        for( i = 0 ; i < BUF_SIZE ; i ++ ) {
            _DeleteOneBuffer( TRUE,mh,i ) ;
        }
    }
}

// 書込みフラグをクリア.
BOOL _ClearWritePos( LPMHDL mh,int no ) {
    BOOL ret ;
    ret = TRUE ;
    // 書込みが存在する場合は、書込み処理を行う.
    if( mh->bufs[ no ].buf != NULL ) {
        memset( mh->bufs[ no ].sectorWriteFlag,NOT_WRITE,MHDL_BUF ) ;
        return TRUE ;
    }
    return FALSE ;
}

// １つのバッファを無効に設定.
BOOL _DeleteOneBuffer( BOOL destroyFlag,LPMHDL mh,int no ) {
    int i ;
    BOOL ret ;
    ret = TRUE ;
    // 書込みが存在する場合は、書込み処理を行う.
    if( mh->bufs[ no ].buf != NULL ) {
        // 書込み処理が行われている場合.
        if( mh->bufs[ no ].usePos != -1 && mh->bufs[ no ].useWriteFlag == USE_WRITE ) {
            int p ;
            LONG64 seek ;
            p = 0 ;
            seek = mh->bufs[ no ].usePos ;
            // 書込みフラグがONになっている内容だけを出力.
            for( i = 0 ; i < MHDL_BUF ; i ++ ) {
                if( mh->bufs[ no ].sectorWriteFlag[ i ] == USE_WRITE ) {
                    _FileWrite( mh,( char* )&(mh->bufs[ no ].buf[p]),seek,mh->sector ) ;
                }
                p += mh->sector ;
                seek += ( LONG64 )mh->sector ;
            }
        }
        // バッファクリア.
        _ClearWritePos( mh,no ) ;
        mh->bufs[ no ].usePos = -1 ;
        mh->bufs[ no ].useLen = -1 ;
        mh->bufs[ no ].useWriteFlag = NOT_WRITE ;
        mh->bufs[ no ].expire = 0 ;
        // バッファ破棄条件の場合.
        if( destroyFlag == TRUE ) {
            if( mh->bufs[ no ].buf != NULL ) {
                free( mh->bufs[ no ].buf ) ;
                mh->bufs[ no ].buf = NULL ;
            }
        }
        return ret ;
    }
    return FALSE ;
}

// バッファを無効に設定.
BOOL _DeleteBuffer( LPMHDL mh ) {
    BOOL ret ;
    int i ;
    ret = TRUE ;
    for( i = 0 ; i < BUF_SIZE ; i ++ ) {
        BOOL res = _DeleteOneBuffer( FALSE,mh,i ) ;
        if( res == FALSE ) {
            ret = FALSE ;
        }
    }
    return ret ;
}

// Mappingに空きが存在するかチェック.
int _UseSpaceBuffer( LPMHDL mh ) {
    int i ;
    for( i = 0 ; i < BUF_SIZE ; i ++ ) {
        if( mh->bufs[ i ].usePos == -1 ) {
            return i ;
        }
    }
    return -1 ;
}

// 指定シークポジションに対する、Mappingを検索.
int _SearchMapping( LPMHDL mh,int addExp,LONG64 pos ) {
    int i ;
    int ret = -1 ;
    for( i = 0 ; i < BUF_SIZE ; i ++ ) {
        if( mh->bufs[ i ].usePos != -1 ) {
            // Mappingの範囲内.
            if( pos >= mh->bufs[ i ].usePos && pos < mh->bufs[ i ].usePos + mh->bufs[ i ].useLen ) {
                // Mapping範囲内のI/Oの場合は、Expire値を初期値にする.
                mh->bufs[ i ].expire = 0 ;
                ret = i ;
                break ;
            }
            // Mapping範囲外.
            //else {
            //    mh->bufs[ i ].expire += addExp ;
            //    if( mh->bufs[ i ].expire >= EXPIRE_BUF ) {
            //        _DeleteOneBuffer( FALSE,mh,i ) ;
            //    }
            //}
        }
    }
    // 一致するBufferが存在しない場合.
    // すべてのMappingに対してExpire値をインクリメント.
    if( ret == -1 ) {
        // Mapping範囲内で平均値範囲内の場合.
        if( mh->average < mh->bufRange + pos && mh->average + mh->bufRange > pos ) {
            for( i = 0 ; i < BUF_SIZE ; i ++ ) {
                if( mh->bufs[ i ].usePos != -1 ) {
                    mh->bufs[ i ].expire += addExp ;
                    if( mh->bufs[ i ].expire >= EXPIRE_BUF ) {
                        _DeleteOneBuffer( FALSE,mh,i ) ;
                    }
                }
            }
        }
    }
    return ret ;
}

// 指定項番に対してMapping.
BOOL _CreateMapping( LPMHDL mh,int mappingPos,LONG64 pos ) {
    int i ;
    LONG64 len ;
    int res ;
    // Mapping条件が不正な場合.
    if( mh->fileLen <= 0 || mh->fileLen <= pos ||
        mh->bufs[ mappingPos ].usePos != -1 ) {
        return FALSE ;
    }
    // バッファ可能範囲外の場合は、Mappingを作成しない.
    if( ( mh->average < mh->bufRange + pos && mh->average + mh->bufRange > pos ) == FALSE ) {
        return FALSE ;
    }
    // Mapping範囲を計算.
    len = mh->len ;
    if( len + pos >= mh->fileLen ) {
        len = mh->fileLen - pos ;
    }
    // 指定位置が他のMappingと重なる可能性があるかチェック.
    for( i = 0 ; i < BUF_SIZE ; i ++ ) {
        if( mh->bufs[ i ].usePos != -1 ) {
            // 他のMappingと開始ポイントが重なった場合.
            if( pos >= mh->bufs[ i ].usePos && pos < mh->bufs[ i ].usePos + mh->bufs[ i ].useLen ) {
                // Mapping処理は行わない.
                return FALSE ;
            }
            // 他のMappingポイントと、今回対象のMappingの長さが重なる場合.
            else if( pos < mh->bufs[ i ].usePos && pos + len >= mh->bufs[ i ].usePos ) {
                return FALSE ;
            }
        }
    }
    // Mapping領域を取得.
    res = _FileRead( mh,mh->bufs[ mappingPos ].buf,pos,( int )len ) ;
    // Mapping処理を行う.
    if( res == -1 ) {
        // Mapping失敗.
        mh->bufs[ mappingPos ].usePos = -1 ;
        mh->bufs[ mappingPos ].useLen = -1 ;
        mh->bufs[ mappingPos ].useWriteFlag = NOT_WRITE ;
        mh->bufs[ mappingPos ].expire = 0 ;
        _ClearWritePos( mh,mappingPos ) ;
        return FALSE ;
    }
    // Mapping成功.
    mh->bufs[ mappingPos ].usePos = pos ;
    mh->bufs[ mappingPos ].useLen = len ;
    mh->bufs[ mappingPos ].useWriteFlag = NOT_WRITE ;
    mh->bufs[ mappingPos ].expire = 0 ;
    _ClearWritePos( mh,mappingPos ) ;
    return TRUE ;
}

// Mappingを含んだRead処理.
int _ReadMapping( char* out,LPMHDL mh,LONG64 pos ) {
    int ret ;
    int mappingPos ;
    char* c ;
    ret = -1 ;
    _Lock( mh ) ;// lock.
    // アクセス平均値を求める.
    if( mh->average <= 0 ) {
        mh->average = pos ;
    }
    else {
        mh->average = ( mh->average + pos ) / 2 ;
    }
    // Mappingが見つかった場合.
    if( ( mappingPos = _SearchMapping( mh,READ_EXPIRE,pos ) ) >= 0 ) {
        int p = ( int )( pos - mh->bufs[ mappingPos ].usePos ) ;
        memcpy( out,( char* )&(mh->bufs[ mappingPos ].buf[p]),mh->sector ) ;
        ret = mh->sector ;
        //mh->maxNotRangeCount = 0 ;
        goto L_SUCCESS ;
    }
    // Mapping領域が見つからない場合は、空き領域を検索し、存在する場合は、
    // そこに対して今回のMapping領域を展開させる.
    if( ( mappingPos = _UseSpaceBuffer( mh ) ) >= 0 ) {
        // 空きMappingが見つかったので、そこに今回の領域に対するMappingを行う.
        if( _CreateMapping( mh,mappingPos,pos ) ) {
            // Mapping成功.
            int p = ( int )( pos - mh->bufs[ mappingPos ].usePos ) ;
            memcpy( out,( char* )&(mh->bufs[ mappingPos ].buf[p]),mh->sector ) ;
            ret = mh->sector ;
            //mh->maxNotRangeCount = 0 ;
            goto L_SUCCESS ;
        }
    }
    // 範囲外である場合.
    //mh->maxNotRangeCount ++ ;
    //if( mh->maxNotRangeCount >= MAX_NOT_RANGE ) {
    //    mh->maxNotRangeCount = 0 ;
    //    mh->average = 0 ;
    //}
    // Mapping対象が存在せず、Mappingできなかった場合は、直接ファイルから取得.
    c = ( char* )valloc( mh->sector ) ;
    memset( c,0,mh->sector ) ;
    ret = _FileRead( mh,c,pos,mh->sector ) ;
    memcpy( out,c,mh->sector ) ;
    free( c ) ;
    c = NULL ;
    
// Mapping成功の場合.
L_SUCCESS :
    _Unlock( mh ) ;// unlock.
    return ret ;
}

// Mappingを含んだWrite処理.
int _WriteMapping( const char* in,LPMHDL mh,LONG64 pos ) {
    int ret ;
    int mappingPos ;
    char* c ;
    ret = -1 ;
    _Lock( mh ) ;// lock.
    // アクセス平均値を求める.
    if( mh->average <= 0 ) {
        mh->average = pos ;
    }
    else {
        mh->average = ( mh->average + pos ) / 2 ;
    }
    // Mappingが見つかった場合.
    if( ( mappingPos = _SearchMapping( mh,WRITE_EXPIRE,pos ) ) >= 0 ) {
        int p = ( int )( pos - mh->bufs[ mappingPos ].usePos ) ;
        memcpy( ( char* )&(mh->bufs[ mappingPos ].buf[p]),in,mh->sector ) ;
        mh->bufs[ mappingPos ].useWriteFlag = USE_WRITE ;
        mh->bufs[ mappingPos ].sectorWriteFlag[ p / mh->sector ] = USE_WRITE ;
        ret = mh->sector ;
        //mh->maxNotRangeCount = 0 ;
        goto L_SUCCESS ;
    }
    // Mapping領域が見つからない場合は、空き領域を検索し、存在する場合は、
    // そこに対して今回のMapping領域を展開させる.
    if( ( mappingPos = _UseSpaceBuffer( mh ) ) >= 0 ) {
        // 空きMappingが見つかったので、そこに今回の領域に対するMappingを行う.
        if( _CreateMapping( mh,mappingPos,pos ) ) {
            // Mapping成功.
            int p = ( int )( pos - mh->bufs[ mappingPos ].usePos ) ;
            memcpy( ( char* )&(mh->bufs[ mappingPos ].buf[p]),in,mh->sector ) ;
            mh->bufs[ mappingPos ].useWriteFlag = USE_WRITE ;
            mh->bufs[ mappingPos ].sectorWriteFlag[ p / mh->sector ] = USE_WRITE ;
            ret = mh->sector ;
            //mh->maxNotRangeCount = 0 ;
            goto L_SUCCESS ;
        }
    }
    // 範囲外である場合.
    //mh->maxNotRangeCount ++ ;
    //if( mh->maxNotRangeCount >= MAX_NOT_RANGE ) {
    //    mh->maxNotRangeCount = 0 ;
    //    mh->average = 0 ;
    //}
    // Mapping対象が存在せず、Mappingできなかった場合は、直接ファイルに書き込む.
    c = ( char* )valloc( mh->sector ) ;
    memcpy( c,in,mh->sector ) ;
    ret = _FileWrite( mh,c,pos,mh->sector ) ;
    free( c ) ;
    c = NULL ;
    
// Mapping成功の場合.
L_SUCCESS :
    _Unlock( mh ) ;// unlock.
    return ret ;
}

///////////////////////////////////////////////////////////////////////////////
// RawI/O実装.
///////////////////////////////////////////////////////////////////////////////

// rawioクローズ.
void CloseRawIO( LPMHDL mh ) {
    if( mh->handle != NULL ) {
        _DestroyBuffer( mh ) ;
        pthread_mutex_destroy(&mh->section);
        close( mh->handle ) ;
        memset( mh,0,sizeof( MHDL ) ) ;
        free( mh ) ;
    }
}

// rawioオープン.
LPMHDL OpenRawIO( int* result,const char* path,const char* drv ) {
    LPMHDL ret ;
    int sectorSize ;
    int handle ;
    handle = open64( path,
        ( O_RDWR | O_DIRECT | O_NOATIME | O_CREAT | O_LARGEFILE | O_SYNC ),
        //( O_RDWR | O_NOATIME | O_CREAT | O_LARGEFILE | O_SYNC ),
        PERMISSION ) ;
    if( handle <= -1 ) {
        *result = 0 ;
        return NULL ;
    }
    // ランダムアクセスで最適化.
    posix_fadvise64( handle, 0, 0, POSIX_FADV_RANDOM ) ;
    // MHDL初期化.
    sectorSize = _GetSector( drv ) ;
    *result = 1 ;
    ret = ( LPMHDL )malloc( sizeof( MHDL ) ) ;
    // ファイル長を取得.
    if( _CreateBuffer( ret,handle,sectorSize ) == FALSE ) {
        // ファイル長の取得に失敗した場合は、生成内容を破棄.
        CloseRawIO( ret ) ;
        *result = 0 ;
        ret = NULL ;
    }
    return ret ;
}

// 指定位置のセクタを書き込む.
int WriteRawIO( LPMHDL mh,const char* data,int pos ) {
    return _WriteMapping( data,mh,_GetPosition( mh->sector,pos ) ) ;
}

// 指定位置のセクタを読み込む.
int ReadRawIO( LPMHDL mh,char* data,int pos ) {
    return _ReadMapping( data,mh,_GetPosition( mh->sector,pos ) ) ;
}

// 現在のファイルサイズを取得.
BOOL GetLengthRawIO( LONG64* out,LPMHDL mh ) {
    _Lock( mh ) ;// lock.
    *out = mh->fileLen ;
    _Unlock( mh ) ;// unlock.
    return TRUE ;
}

// ファイルサイズをセット.
BOOL SetLengthRawIO( LPMHDL mh,LONG64 len ) {
    return _SetFileLenght( mh,len ) ;
}

// セクタ数を取得.
int GetSector( LPMHDL mh ) {
    int ret ;
    _Lock( mh ) ;// lock.
    ret = mh->sector ;
    _Unlock( mh ) ;// unlock.
    return ret ;
}

// セクタ数を取得.
int GetSectorByPath( const char* path ) {
    return _GetSector( path ) ;
}
