#include <windows.h>
#include "../../include/org_maachang_jni_io_NativeIO.h"

#pragma comment(lib,"jvm.lib")

//-------------------------------------------------------------------------------------------------
// win32用mmap定義.
//-------------------------------------------------------------------------------------------------

#include <io.h>
#include <stddef.h>
#include <sys/types.h>

// ページファイル関連定義.
typedef struct {
    unsigned int length ;
    unsigned int shift ;
    unsigned int mask ;
} _MAACHANG_PAGE_FILE ;
static _MAACHANG_PAGE_FILE _pageFile ;

// ページファイルサイズを初期化.
void initPageFileSize() {
    SYSTEM_INFO info ;
    GetSystemInfo( &info ) ;
    //_pageFile.length = info.dwPageSize ;
    _pageFile.length = info.dwAllocationGranularity ;
    _pageFile.mask = _pageFile.length - 1 ;
    switch( _pageFile.mask ) {
        case 0x00000000 : _pageFile.shift = 0 ; break ;
        case 0x00000001 : _pageFile.shift = 1 ; break ;
        case 0x00000003 : _pageFile.shift = 2 ; break ;
        case 0x00000007 : _pageFile.shift = 3 ; break ;
        case 0x0000000f : _pageFile.shift = 4 ; break ;
        case 0x0000001f : _pageFile.shift = 5 ; break ;
        case 0x0000003f : _pageFile.shift = 6 ; break ;
        case 0x0000007f : _pageFile.shift = 7 ; break ;
        case 0x000000ff : _pageFile.shift = 8 ; break ;
        case 0x000001ff : _pageFile.shift = 9 ; break ;
        case 0x000003ff : _pageFile.shift = 10 ; break ;
        case 0x000007ff : _pageFile.shift = 11 ; break ;
        case 0x00000fff : _pageFile.shift = 12 ; break ;
        case 0x00001fff : _pageFile.shift = 13 ; break ;
        case 0x00003fff : _pageFile.shift = 14 ; break ;
        case 0x00007fff : _pageFile.shift = 15 ; break ;
        case 0x0000ffff : _pageFile.shift = 16 ; break ;
        case 0x0001ffff : _pageFile.shift = 17 ; break ;
        case 0x0003ffff : _pageFile.shift = 18 ; break ;
        case 0x0007ffff : _pageFile.shift = 19 ; break ;
        case 0x000fffff : _pageFile.shift = 20 ; break ;
        case 0x001fffff : _pageFile.shift = 21 ; break ;
        case 0x003fffff : _pageFile.shift = 22 ; break ;
        case 0x007fffff : _pageFile.shift = 23 ; break ;
        case 0x00ffffff : _pageFile.shift = 24 ; break ;
        case 0x01ffffff : _pageFile.shift = 25 ; break ;
        case 0x03ffffff : _pageFile.shift = 26 ; break ;
        case 0x07ffffff : _pageFile.shift = 27 ; break ;
        case 0x0fffffff : _pageFile.shift = 28 ; break ;
        case 0x1fffffff : _pageFile.shift = 29 ; break ;
        case 0x3fffffff : _pageFile.shift = 30 ; break ;
        case 0x7fffffff : _pageFile.shift = 31 ; break ;
    }
}

#define _MAACHANG_MMAP_OPT_WRITE 2

// win32用mmap生成.
void* win32_mmap( HANDLE* out,HANDLE handle,int option,__int64 offset,size_t length ) {
    void *p;
    HANDLE mhnd ;
    DWORD flProtect;
    DWORD dwDesiredAccess;
    __int64 mapLen ;
    
    flProtect = PAGE_READONLY;
    dwDesiredAccess = FILE_MAP_READ;
    if (option == _MAACHANG_MMAP_OPT_WRITE) {
        flProtect = PAGE_READWRITE ;
        dwDesiredAccess |= FILE_MAP_WRITE;
    }
    mapLen = offset + (__int64)length ;
    mhnd = CreateFileMapping(handle, NULL, flProtect,
        ( DWORD )( ( mapLen & 0xffffffff00000000 ) >> 32 ),
        ( DWORD )( mapLen & 0x00000000ffffffff ), NULL ) ;
    if( mhnd == INVALID_HANDLE_VALUE ) {
        return NULL ;
    }
    p = MapViewOfFile( mhnd, dwDesiredAccess,
        ( DWORD )( ( offset & 0xffffffff00000000 ) >> 32 ),
        ( DWORD )( offset & 0x00000000ffffffff ),length );
    if( p == NULL ) {
        CloseHandle(mhnd) ;
        return NULL ;
    }
    *out = mhnd ;
    return p;
}

// win32用mmap破棄.
int win32_munmap( void *addr,int length ) {
    return UnmapViewOfFile(addr) ;
}

// win32用mmap更新.
int win32_msync( void *addr,size_t length ) {
    return (FlushViewOfFile(addr, length) != 0) ? 0 : -1;
}

//-------------------------------------------------------------------------------------------------
// win32用ファイルI/O定義.
//-------------------------------------------------------------------------------------------------

/** シークセット. **/
BOOL win32_putSeek( HANDLE handle,__int64 seek ) {
    unsigned long low,high,res ;
    if( seek <= -1 ) {
        return FALSE ;
    }
    low = ( unsigned long )( seek & 0x00000000ffffffff ) ;
    high = ( unsigned long )( ( seek & 0xffffffff00000000 ) >> 32 ) ;
    if( high == 0 ){
        res = SetFilePointer( handle,low,NULL,FILE_BEGIN ) ;
    }else{
        res = SetFilePointer( handle,low,(long*)&high,FILE_BEGIN ) ;
    }
    if( GetLastError() != NO_ERROR ) {
        return FALSE ;
    }
    return TRUE ;
}

/** シーク位置を取得. **/
__int64 win32_getSeek( HANDLE handle ) {
    unsigned long low,high ;
    low = SetFilePointer( handle,0,(long*)&high,FILE_CURRENT ) ;
    if( GetLastError() != NO_ERROR ) {
        return -1 ;
    }
    return ( __int64 )( ( ( __int64 )low & 0x00000000ffffffff ) |
        ( ( ( __int64 )high & 0x00000000ffffffff ) << 32 ) ) ; ;
}

#define MAACHANG_RANDOM_IO 0
#define MAACHANG_FILE_READ 0
#define MAACHANG_FILE_WRITE 1
#define MAACHANG_FILE_RW 2

// ファイルオープン.
HANDLE win32_fileOpen( int mode,unsigned int rw,const char* fileName ) {
    HANDLE ret ;
    DWORD flg ;
    DWORD rwMode ;
    flg = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED ;    // Indexを利用しない.
    if( mode == MAACHANG_RANDOM_IO ) {
        flg = flg | FILE_FLAG_RANDOM_ACCESS |     // ランダムアクセスに特化.
            FILE_FLAG_POSIX_SEMANTICS ;           // POSIX仕様.
    }
    else {
        flg |= FILE_FLAG_SEQUENTIAL_SCAN ;        // シーケンスに特化.
    }
    // 読み込み専用モード.
    if( rw == MAACHANG_FILE_READ ) {
        rwMode = GENERIC_READ ;
    }
    // 書き込み専用モード.
    else if( rw == MAACHANG_FILE_WRITE ) {
        rwMode = GENERIC_WRITE ;
    }
    // それ以外の設定の場合は、読み書きモード.
    else {
        rwMode = (GENERIC_READ | GENERIC_WRITE) ;
    }
    ret = CreateFile(
        fileName,
        rwMode,
        0,
        NULL,
        OPEN_ALWAYS,// ファイルがなければ作成、あれば、そのまま利用する.
        flg,
        NULL ) ;
    if( ret == INVALID_HANDLE_VALUE ) {
        return NULL ;
    }
    return ret ;
}

// ファイルクローズ.
void win32_fileClose( HANDLE handle ) {
    if( handle != INVALID_HANDLE_VALUE ) {
        CloseHandle( handle ) ;
    }
}

/** ファイルバッファを強制出力 **/
BOOL win32_fsync( HANDLE handle ) {
    return FlushFileBuffers( handle ) ;
}

// ファイル読み込み処理.
int win32_fileRead( HANDLE handle,char* out,__int64 seek,int off,int len ) {
    int ret ;
    BOOL res ;
    if( handle == INVALID_HANDLE_VALUE ) {
        return -1 ;
    }
    win32_putSeek( handle,seek ) ;
    res = ReadFile( handle,( char* )(out+off),len,( unsigned int* )&ret,NULL ) ;
    if( res == FALSE ) {
        ret = -1 ;
    }
    return ret ;
}

// ファイル書込み処理.
int win32_fileWrite( HANDLE handle,char* in,__int64 seek,int off,int len ) {
    BOOL res ;
    int ret ;
    ret = 0 ;
    if( handle == INVALID_HANDLE_VALUE ) {
        return -1 ;
    }
    win32_putSeek( handle,seek ) ;
    res = WriteFile( handle,( char* )(in+off),len,&ret,NULL ) ;
    if( res == FALSE ) {
        return -1 ;
    }
    return ret ;
}

/** ファイルサイズを取得 **/
__int64 win32_getFileLength( HANDLE handle ) {
    unsigned long sizeLow = 0 ;
    unsigned long sizeHigh = 0 ;
    if( handle == INVALID_HANDLE_VALUE ) {
        return -1 ;
    }
    sizeLow = GetFileSize( handle,&sizeHigh ) ;
    if( sizeLow == 0xffffffff ) {
        if( GetLastError() != ERROR_SUCCESS ) {
            return -1 ;
        }
    }
    return ( __int64 )( ( ( __int64 )sizeLow & 0x00000000ffffffff ) |
        ( ( ( __int64 )sizeHigh & 0x00000000ffffffff ) << 32 ) ) ; ;
}

/** ファイルサイズを設定 **/
int win32_setFileLength( HANDLE handle,__int64 len ) {
    long low,high ;
    unsigned long res ;
    if( handle == INVALID_HANDLE_VALUE ) {
        return -1 ;
    }
    low = ( unsigned long )( len & 0x00000000ffffffff ) ;
    high = ( unsigned long )( ( len & 0xffffffff00000000 ) >> 32 ) ;
    res = SetFilePointer( handle,low,&high,FILE_BEGIN ) ;
    if( res == 0xffffffff ) {
        if( GetLastError() != NO_ERROR ) {
            return -1 ;
        }
    }
    if( SetEndOfFile( handle ) == FALSE ) {
        return -1 ;
    }
    return 0 ;
}

//*************************************************************************************************
// JNI定義(初期化処理).
//*************************************************************************************************

/** init. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_init
  (JNIEnv* env, jclass c) {
    // ページファイル条件初期化.
    initPageFileSize() ;
    return 0 ;
}

//*************************************************************************************************
// JNI定義(MemoryI/O).
//*************************************************************************************************

/** malloc. **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_malloc
  (JNIEnv* env, jclass c, jint size) {
    return (jlong)malloc( size ) ;
}

/** realloc. **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_realloc
  (JNIEnv* env, jclass c, jlong addr, jint size) {
    return (jlong)realloc( (void*)addr,size ) ;
}

/** free. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_free
  (JNIEnv* env, jclass c, jlong addr) {
    free( (void*)addr ) ;
}

/** memset. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_memset
  (JNIEnv* env, jclass c, jlong addr, jbyte code, jint size ) {
    memset( (void*)addr,code,size ) ;
}

/** memcpy. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_memcpy
  (JNIEnv* env, jclass c, jlong srcAddr, jlong destAddr, jint size ) {
    memcpy( (void*)srcAddr,(void*)destAddr,size ) ;
}

/** putByte. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_putByte
  (JNIEnv* env, jclass c, jlong addr, jbyte value ) {
    *((char*)addr) = value ;
}

/** getByte. **/
JNIEXPORT jbyte JNICALL Java_org_maachang_jni_io_NativeIO_getByte
  (JNIEnv* env, jclass c, jlong addr ) {
    return *((char*)addr) ;
}

/** getBinary. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_getBinary
  (JNIEnv* env, jclass c, jlong addr, jbyteArray bin, jint off, jint len ) {
    (*env)->SetByteArrayRegion( env,bin,off,len,(char*)addr ) ;
}

/** putBinary. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_putBinary
  (JNIEnv* env, jclass c, jlong addr, jbyteArray bin, jint off, jint len ) {
    (*env)->GetByteArrayRegion( env,bin,off,len,(char*)addr ) ;
}

/** putChar **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_putChar
  (JNIEnv* env, jclass c, jlong addr, jchar value) {
    *((jchar*)addr) = value ;
}

/** getChar **/
JNIEXPORT jchar JNICALL Java_org_maachang_jni_io_NativeIO_getChar
  (JNIEnv* env, jclass c, jlong addr) {
    return *((jchar*)addr) ;
}

/** putShort **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_putShort
  (JNIEnv* env, jclass c, jlong addr, jshort value) {
    *((jshort*)addr) = value ;
}

/** getShort **/
JNIEXPORT jshort JNICALL Java_org_maachang_jni_io_NativeIO_getShort
  (JNIEnv* env, jclass c, jlong addr) {
    return *((jshort*)addr) ;
}

/** putInt **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_putInt
  (JNIEnv* env, jclass c, jlong addr, jint value) {
    *((jint*)addr) = value ;
}

/** getInt **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_getInt
  (JNIEnv* env, jclass c, jlong addr) {
    return *((jint*)addr) ;
}

/** putLong **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_putLong
  (JNIEnv* env, jclass c, jlong addr, jlong value) {
    *((jlong*)addr) = value ;
}

/** getLong **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_getLong
  (JNIEnv* env, jclass c, jlong addr) {
    return *((jlong*)addr) ;
}

/** binary同一チェック. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_equalsBinary
  (JNIEnv* env, jclass c,jlong c1, jlong c2, jint len ) {
    int i ;
    char* v1 ;
    char* v2 ;
    v1 = (char*)(c1) ;
    v2 = (char*)(c2) ;
    for( i = 0 ; i < len ; i ++ ) {
        if( *(v1+i) != *(v2+i) ) {
            return 0 ;
        }
    }
    return 1 ;
}

#define _FNV_64_HASH 0xCBF29CE484222325

/** fnvHash64. **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_fnv64
  (JNIEnv* env, jclass c, jlong addr,jint len ) {
    int i ;
    jlong ret ;
    char* v ;
    ret = _FNV_64_HASH ;
    v = (char*)(addr) ;
    for( i = 0 ; i < len ; i ++ ) {
        ret ^= *(v+i);
        ret += (ret << 1L) + (ret << 4L) + (ret << 5L) + (ret << 7L)
                + (ret << 8L) + (ret << 40L);
    }
    return ret ;
}

/** IndexOf **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_indexOf
  (JNIEnv* env, jclass c, jlong addr, jint length, jbyteArray bin, jint binLen) {
    int i ;
    jint ret = -1 ;
    int cnt = 0 ;
    char* b = (char*)(addr) ;
    char* v = (char*)malloc( binLen ) ;
    (*env)->GetByteArrayRegion( env,bin,0,binLen,(char*)v ) ;
    for( i = 0 ; i < length ; i ++ ) {
        if( b[ i ] == v[ cnt ] ) {
            cnt ++ ;
            if( cnt >= binLen ) {
                free( v ) ;
                v = NULL ;
                return (jint)( ( i - binLen ) + 1 ) ;
            }
        }
        else {
            cnt = 0 ;
        }
    }
    free( v ) ;
    v = NULL ;
    return -1 ;
}

/** lastIndexOf **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_lastIndexOf
  (JNIEnv* env, jclass c, jlong addr, jint offset, jint length, jbyteArray bin, jint binLen) {
    int i ;
    jint ret = -1 ;
    int startCnt = binLen - 1 ;
    int cnt = startCnt ;
    char* b = (char*)(addr) ;
    char* v = (char*)malloc( binLen ) ;
    (*env)->GetByteArrayRegion( env,bin,0,binLen,(char*)v ) ;
    for( i = length-offset ; i >= 0 ; i -- ) {
        if( b[ i ] == v[ cnt ] ) {
            cnt -- ;
            if( cnt < 0 ) {
                free( v ) ;
                v = NULL ;
                return (jint)i ;
            }
        }
        else {
            cnt = startCnt ;
        }
    }
    free( v ) ;
    v = NULL ;
    return -1 ;
}


//*************************************************************************************************
// JNI定義(FileI/O).
//*************************************************************************************************

/** ファイルオープン. **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_open
  (JNIEnv* env, jclass c, jint mode, jint rw, jbyteArray bpath ) {
    char* path ;
    HANDLE ret ;
    path = (*env)->GetPrimitiveArrayCritical(env, bpath, NULL);
    // ファイルオープン.
    ret = win32_fileOpen( mode,rw,path ) ;
    (*env)->ReleasePrimitiveArrayCritical(env, bpath, path, JNI_ABORT);
    if( ret == INVALID_HANDLE_VALUE ) {
        return ( jlong )-1 ;
    }
    return ( jlong )ret ;
}

/** ファイルクローズ. **/
JNIEXPORT void JNICALL Java_org_maachang_jni_io_NativeIO_close
  (JNIEnv* env, jclass c, jlong h ) {
    win32_fileClose( ( HANDLE )h ) ;
}

/** ファイル更新. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_fsync
  (JNIEnv* env, jclass c, jlong h ) {
    BOOL ret ;
    ret = win32_fsync( ( HANDLE )h ) ;
    if( ret == TRUE ) {
        return 0 ;
    }
    return -1 ;
}

/** 読み込み処理. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_read
  (JNIEnv* env, jclass c, jlong h, jlong seek, jlong address, jint off, jint len) {
    return ( jint )win32_fileRead( ( HANDLE )h,(char*)address,seek,off,len ) ;
}

/** 書込み処理 **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_write
  (JNIEnv* env, jclass c, jlong h, jlong seek, jlong address, jint off, jint len) {
    return ( jint )win32_fileWrite( ( HANDLE )h,(char*)address,seek,off,len ) ;
}

/** ファイルサイズを取得 **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_getLength
  (JNIEnv* env, jclass c, jlong h ) {
    return win32_getFileLength( ( HANDLE )h ) ;
}

/** ファイルサイズを設定 **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_setLength
  (JNIEnv* env, jclass c, jlong h , jlong len ) {
    return win32_setFileLength( ( HANDLE )h,len ) ;
}

/** シーク位置を取得. **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_getSeek
  (JNIEnv* env, jclass c, jlong h ) {
    return win32_getSeek( ( HANDLE )h ) ;
}

//*************************************************************************************************
// JNI定義(mmapI/O).
//*************************************************************************************************

/** ページファイルサイズを取得. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_pageFileLength
  (JNIEnv* env, jclass c) {
    return _pageFile.length ;
}

/** ページファイルサイズのシフト数を取得. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_pageFileShift
  (JNIEnv* env, jclass c) {
    return _pageFile.shift ;
}

/** mmapデータ長調整処理. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_mmapLength
  (JNIEnv* env, jclass c, jint length) {
    return length ;
}

/** mmap生成 **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_createMmap
  (JNIEnv* env, jclass c, jlongArray out,jlong h, jint opt, jlong offset, jint length) {
    void* addr ;
    jlong o1,o2 ;
    HANDLE outHandle ;
    addr = win32_mmap( &outHandle,(HANDLE)h,opt,offset,length ) ;
    if( addr == NULL ) {
        return -1 ;
    }
    o1 = (jlong)addr ;
    o2 = (jlong)outHandle ;
    (*env)->SetLongArrayRegion( env,out,0,1,&o1 ) ;
    (*env)->SetLongArrayRegion( env,out,1,1,&o2 ) ;
    return 0 ;
}

/** mmap破棄 **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_closeMmap
  (JNIEnv* env, jclass c, jlong mmapHandle,jlong addr, jint length) {
    win32_msync( (void*)addr,length ) ;
    CloseHandle( (HANDLE)mmapHandle ) ;
    return win32_munmap( (void*)addr,length ) ;
}

/** mmap強制書き込み **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_flushMmap
  (JNIEnv* env, jclass c, jlong addr, jint length) {
    return win32_msync( (void*)addr,length ) ;
}

