package com.jetbrains.cef;

import com.jetbrains.cef.remote.NativeServerManager;
import org.cef.OS;
import org.cef.SystemBootstrap;
import org.cef.misc.CefLog;
import org.cef.misc.Utils;

import java.io.File;
import java.nio.ByteBuffer;

public class SharedMemory {
    private static final String ALT_MEM_HELPER_PATH;
    private static volatile boolean isLoaded = false;
    public final String mname;
    public final long boostHandle;
    public long lasUsedMs = 0;

    final private long mySegment;
    final private long myPtr;

    final private long myMutex;

    static {
        final String altMemHelperPath = Utils.getString("ALT_MEM_HELPER_PATH");
        if (altMemHelperPath != null && !altMemHelperPath.trim().isEmpty())
            ALT_MEM_HELPER_PATH = altMemHelperPath.trim();
        else if (NativeServerManager.ALT_CEF_SERVER_PATH != null && !NativeServerManager.ALT_CEF_SERVER_PATH.trim().isEmpty()) {
            File libDir = new File(NativeServerManager.ALT_CEF_SERVER_PATH).getParentFile();
            String libName;
            if (OS.isWindows())
                libName = "shared_mem_helper.dll";
            else if (OS.isLinux())
                libName = "libshared_mem_helper.so";
            else {
                // libshared_mem_helper.dylib is usually place near cef_server.app but ALT_CEF_SERVER_PATH is like cef_server.app/Contents/MacOS/cef_server
                libDir = libDir.getParentFile().getParentFile().getParentFile();
                libName = "libshared_mem_helper.dylib";
            }

            ALT_MEM_HELPER_PATH = new File(libDir, libName).getAbsolutePath();
        } else
            ALT_MEM_HELPER_PATH = null;
        loadDynamicLib();
    }

    public static boolean isIsLoaded() { return isLoaded; }

    public static void loadDynamicLib() {
        if (isLoaded)
            return;
        try {
            if (ALT_MEM_HELPER_PATH == null || ALT_MEM_HELPER_PATH.isEmpty()) {
                CefLog.Debug("Load shared_mem_helper library from jbr bundle.");
                SystemBootstrap.loadLibrary("shared_mem_helper");
            } else {
                CefLog.Debug("Load shared_mem_helper library from alt path %s", ALT_MEM_HELPER_PATH);
                System.load(ALT_MEM_HELPER_PATH.trim());
            }
            isLoaded = true;
        } catch (UnsatisfiedLinkError e) {
            CefLog.Error("Can't load shared_mem_helper, exception: %s", e.getMessage());
        }
    }

    public static void loadDynamicLib(String path) {
        if (isLoaded)
            return;
        try {
            CefLog.Debug("Load shared_mem_helper library from file %s", path);
            System.load(path.trim());
            isLoaded = true;
        } catch (UnsatisfiedLinkError e) {
            CefLog.Error("Can't load shared_mem_helper, exception: %s", e.getMessage());
        }
    }

    public SharedMemory(String sharedMemName, long boostHandle) {
        mname = sharedMemName;
        this.boostHandle = boostHandle;
        this.mySegment = openSharedSegment(sharedMemName);
        this.myPtr = getPointer(mySegment, boostHandle);

        myMutex = openSharedMutex(sharedMemName);
    }

    public void lock() {
        lockSharedMutex(myMutex);
    }

    public void unlock() {
        unlockSharedMutex(myMutex);
    }

    public long getPtr() {
        return myPtr;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            closeSharedSegment(mySegment);
            closeSharedMutex(myMutex);
        } finally {
            super.finalize();
        }
    }

    public ByteBuffer wrap(int size) {
        return wrapNativeMem(myPtr, size);
    }

    public int readInt() {
        return readInt(myPtr, 0);
    }

    public int readInt(int offset) {
        return readInt(myPtr, offset);
    }

    public int readByte(int offset) {
        return readByte(myPtr, offset);
    }

    // Helper method (for creating BufferedImage from native raster)
    private static native ByteBuffer wrapNativeMem(long pdata, int length);

    private static native int readInt(long pdata, int offset);

    private static native int readByte(long pdata, int offset);
    
    public static class WithRaster extends SharedMemory {
        private int myWidth;
        private int myHeight;
        private int myDirtyRectsCount;

        public WithRaster(String sharedMemName, long boostHandle) {
            super(sharedMemName, boostHandle);
        }

        public ByteBuffer wrapRaster() {
            return wrapNativeMem(getPtr(), myWidth * myHeight * 4);
        }
        public ByteBuffer wrapRects() {
            return wrapNativeMem(getPtr() + getRectsOffset(), myDirtyRectsCount * 4 * 4);
        }

        public int getRectsOffset() { return myWidth * myHeight * 4; }

        public int getWidth() {
            return myWidth;
        }

        public void setWidth(int width) {
            this.myWidth = width;
        }

        public int getHeight() {
            return myHeight;
        }

        public void setHeight(int height) {
            this.myHeight = height;
        }

        public int getDirtyRectsCount() {
            return myDirtyRectsCount;
        }

        public void setDirtyRectsCount(int dirtyRectsCount) {
            this.myDirtyRectsCount = dirtyRectsCount;
        }
    }

    //
    // Private native API
    //
    private static native long openSharedSegment(String sid);
    private static native long getPointer(long segment, long handle);
    private static native void closeSharedSegment(long segment);

    private static native long openSharedMutex(String uid);
    private static native void lockSharedMutex(long mutex);
    private static native void unlockSharedMutex(long mutex);
    private static native void closeSharedMutex(long mutex);
}
