/*
 *  Copyright 2015 Brian S O'Neill
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.cojen.tupl.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.EnumSet;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

/**
 * 
 *
 * @author Brian S O'Neill
 */
class PosixMappedPageArray extends MappedPageArray {
    private static Posix cPosix;

    static {
        cPosix = (Posix) Native.loadLibrary(Platform.C_LIBRARY_NAME, Posix.class);
    }

    private final int mFileDescriptor;

    private volatile boolean mEmpty;

    PosixMappedPageArray(int pageSize, long pageCount,
                         File file, EnumSet<OpenOption> options)
        throws IOException
    {
        super(pageSize, pageCount, options);

        mEmpty = file.length() == 0;

        // Select O_RDONLY or O_RDWR.
        int flags = 0;
        if (!options.contains(OpenOption.READ_ONLY)) {
            flags |= 2;
            if (options.contains(OpenOption.CREATE)) {
                file.createNewFile();
            }
        }

        int fd = cPosix.open(file.getPath(), flags);

        if (fd == -1) {
            int error = Native.getLastError();
            throw toException(error);
        }

        long mappingSize = pageSize * pageCount;

        long fileLen = file.length();
        if (fileLen < mappingSize) {
            if (options.contains(OpenOption.READ_ONLY)) {
                throw new IOException("File is too shart: " + fileLen + " < " + mappingSize);
            }
            // Grow the file or else accessing the mapping will seg fault.
            if (cPosix.ftruncate(fd, mappingSize) == -1) {
                int error = Native.getLastError();
                cPosix.close(fd);
                throw toException(error);
            }
        }

        int prot = 1 | 2; // PROT_READ | PROT_WRITE
        flags = 1; // MAP_SHARED

        long ptr = cPosix.mmap(0, mappingSize, prot, flags, fd, 0);

        if (ptr == -1) {
            int error = Native.getLastError();
            cPosix.close(fd);
            throw toException(error);
        }

        mFileDescriptor = fd;

        setMappingPtr(ptr);
    }

    @Override
    public long getPageCount() {
        return mEmpty ? 0 : super.getPageCount();
    }

    @Override
    public void setPageCount(long count) {
        mEmpty = count == 0;
    }

    void doSync(long mappingPtr, boolean metadata) throws IOException {
        int flags = 4; // MS_SYNC
        if (cPosix.msync(mappingPtr, super.getPageCount() * pageSize(), flags) == -1) {
            int error = Native.getLastError();
            throw toException(error);
        }

        if (metadata && cPosix.fsync(mFileDescriptor) == -1) {
            int error = Native.getLastError();
            throw toException(error);
        }
    }

    void doSyncPage(long mappingPtr, long index) throws IOException {
        int pageSize = pageSize();
        int flags = 4; // MS_SYNC
        if (cPosix.msync(mappingPtr + index * pageSize, pageSize, flags) == -1) {
            int error = Native.getLastError();
            throw toException(error);
        }
    }

    void doClose(long mappingPtr) throws IOException {
        cPosix.munmap(mappingPtr, super.getPageCount() * pageSize());
        cPosix.close(mFileDescriptor);
    }

    private static IOException toException(int error) {
        return new IOException(cPosix.strerror_r(error, null, 0));
    }

    public static interface Posix extends Library {
        String strerror_r(int errnum, char[] buf, int buflen);

        int open(String path, int oflag);

        int ftruncate(int fd, long length);

        int fsync(int fd);

        int close(int fd);

        long mmap(long addr, long length, int prot, int flags, int fd, long offset);

        int msync(long addr, long length, int flags);

        int munmap(long addr, long length);
    }
}

@@ -1352,75 +1352,83 @@
     
-mEmpty = file.length() == 0;%0A%0A        // Select O_RDONLY or O_RDWR.
+// Create file (if necessary) and get proper length.%0A%0A        long fileLen;
 %0A   
@@ -1441,26 +1441,20 @@
 nt f
-lags = 0
+d
 ;%0A
+%0A
         
 if (
@@ -1453,54 +1453,187 @@
     
-if (!options.contains(OpenOption.READ_ONLY)) %7B
+JavaFileIO fio = new JavaFileIO(file, options, 1);%0A        try %7B%0A            fileLen = fio.length();%0A%0A            mEmpty = fileLen == 0;%0A%0A            // Select O_RDONLY or O_RDWR.
 %0A   
@@ -1645,18 +1645,21 @@
     
+int 
 flags 
-%7C
 = 
-2
+0
 ;%0A  
@@ -1664,32 +1664,33 @@
             if (
+!
 options.contains
@@ -1705,14 +1705,17 @@
 ion.
-C
 REA
-TE
+D_ONLY
 )) %7B
@@ -1736,27 +1736,17 @@
    f
-ile.createNewFile()
+lags %7C= 2
 ;%0A  
@@ -1752,34 +1752,24 @@
            %7D
-%0A        %7D
 %0A%0A        in
@@ -1766,19 +1766,19 @@
         
-int
+   
  fd = cP
@@ -1804,32 +1804,36 @@
 ath(), flags);%0A%0A
+    
         if (fd =
@@ -1844,32 +1844,36 @@
 ) %7B%0A            
+    
 int error = Nati
@@ -1895,32 +1895,36 @@
 ();%0A            
+  
+  
 throw toExceptio
@@ -1925,32 +1925,91 @@
 ception(error);%0A
+            %7D%0A        %7D finally %7B%0A            fio.close();%0A
         %7D%0A%0A     
@@ -2057,46 +2057,8 @@
 t;%0A%0A
-        long fileLen = file.length();%0A
     
@@ -2201,17 +2201,17 @@
 s too sh
-a
+o
 rt: %22 + 
@@ -2598,24 +2598,28 @@
 ITE%0A        
+int 
 flags = 1; /

Fix mapping block devices.