#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64

#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
//#include <stdio.h>
//#include <sys/uio.h>
#include <sys/mman.h>

#include "../../include/org_maachang_jni_io_NativeIO.h"

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

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

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

// ページファイルサイズを初期化.
void initPageFileSize() {
    _pageFile.length = sysconf( _SC_PAGESIZE ) ;
    _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 ;
    }
}

/** ファイルポイント位置を設定. **/
jlong linux_setSeek( int fp,jlong pos ) {
    return lseek64( fp,pos,SEEK_SET ) ;
}

/** ファイルポイント位置を取得. **/
jlong linux_getSeek( int fp ) {
    return lseek64( fp,0,SEEK_CUR ) ;
}

#define _MAACHANG_FOPEN_READ 0
#define _MAACHANG_FOPEN_WRITE 1

/** Linux用ファイルオープン. **/
int linux_fileOpen( int mode,int rw,const char* path ) {
    int fp ;
    unsigned rwMode ;
    if( rw == _MAACHANG_FOPEN_READ ) {
        // 読み込み専用.
        rwMode = O_RDONLY ;
    }
    else if( rw == _MAACHANG_FOPEN_WRITE ) {
        // 書き込み専用.
        rwMode = O_WRONLY | O_CREAT | O_LARGEFILE ;
    }
    else {
        // 読み書き両方.
        rwMode = O_RDWR | O_CREAT | O_LARGEFILE ;
    }
    // ファイルオープン.
    if( ( fp = open64( path,rwMode  | O_NOATIME,PERMISSION ) ) <= -1 ) {
        return -1 ;
    }
    if( mode == 0 ) {
        // randomアクセスに特化.
        if( posix_fadvise64( fp, 0, 0, POSIX_FADV_RANDOM ) <= -1 ) {
            close( fp ) ;
            return -1 ;
        }
    }
    else {
        // シーケンスアクセスに特化.
        if( posix_fadvise64( fp, 0, 0, POSIX_FADV_SEQUENTIAL ) <= -1 ) {
            close( fp ) ;
            return -1 ;
        }
    }
    return fp ;
}

/** Linux用ファイルクローズ. **/
void linux_fileClose( int fp ) {
    close( fp ) ;
}

/** Linux用ファイルキャッシュ出力. **/
int linux_fsync( int fp ) {
    //return fsync( fp ) ;
    return fdatasync( fp ) ;
}

/** Linux用ファイル読み込み処理 **/
int linux_fileRead( int fp,char* data,jlong seek,int off,int len ) {
    int ret ;
    if( seek <= -1 ) {
        ret = read( fp,data+off,len ) ;
    }
    else {
        ret = pread64( fp,data+off,len,seek ) ;
    }
    if( ret <= -1 ) {
        return -1 ;
    }
    return ret ;
}

/** Linux用書き込み処理. **/
int linux_fileWrite( int fp,char* data,jlong seek,int off,int len ) {
    int ret ;
    if( seek <= -1 ) {
        ret = write( fp,data+off,len ) ;
    }
    else {
        ret = pwrite64( fp,data+off,len,seek ) ;
    }
    if( ret <= -1 ) {
        return -1 ;
    }
    return ret ;
}

/** Linux用ファイルサイズ取得. **/
jlong linux_getFileLength( int fp ) {
    struct stat64 buf;
    int ret;
    ret = fstat64( fp,&buf );
    if( ret >= 0 ) {
        return buf.st_size ;
    }
    return -1 ;
}

/** Linux用ファイルサイズ設定. **/
int linux_setFileLength( int fp,jlong len ) {
    return ftruncate64( fp,len ) ;
}

//*************************************************************************************************
// 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 ) {
    int fp ;
    char* path = (*env)->GetPrimitiveArrayCritical(env, bpath, NULL);
    fp = linux_fileOpen( mode,rw,path ) ;
    (*env)->ReleasePrimitiveArrayCritical(env, bpath, path, JNI_ABORT);
    return ( jlong )fp ;
}

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

/** ファイル更新. **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_fsync
  (JNIEnv* env, jclass c, jlong h ) {
    return linux_fsync( ( int )h ) ;
}

/** 読み込み処理. **/
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 )linux_fileRead( (int)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 )linux_fileWrite( (int)h,(char*)address,seek,off,len ) ;
}

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

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

/** シーク位置を取得. **/
JNIEXPORT jlong JNICALL Java_org_maachang_jni_io_NativeIO_getSeek
  (JNIEnv* env, jclass c, jlong h ) {
    return linux_getSeek( ( int )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 & ~_pageFile.mask ) >> _pageFile.shift ) +
        ( ( ( length & _pageFile.mask ) != 0 ) ? 1 : 0 )
    ) * _pageFile.length ;
}

#define _MAACHANG_MMAP_OPT_WRITE 2

/** 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) {
    char* addr ;
    int prot ;
    jlong o1 ;
    // I/O条件をセット.
    if( opt == _MAACHANG_MMAP_OPT_WRITE ) {
        prot = PROT_READ | PROT_WRITE | PROT_EXEC ;
    }
    else {
        prot = PROT_READ | PROT_EXEC ;
    }
    //addr = mmap( NULL,length,prot,MAP_PRIVATE,(int)h,(size_t)offset ) ;
    addr = mmap( NULL,length,prot,MAP_SHARED,(int)h,(size_t)offset ) ;
    if( addr == MAP_FAILED ) {
        return -1 ;
    }
    o1 = (jlong)addr ;
    (*env)->SetLongArrayRegion( env,out,0,1,&o1 ) ;
    (*env)->SetLongArrayRegion( env,out,1,1,&o1 ) ;
    return 0 ;
}

/** mmap破棄 **/
JNIEXPORT jint JNICALL Java_org_maachang_jni_io_NativeIO_closeMmap
  (JNIEnv* env, jclass c, jlong mmapHandle, jlong addr, jint length) {
    msync((char*)addr,length,MS_SYNC) ;
    return munmap((char*)addr,length) ;
}

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

