/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.ignite.internal.util.ipc.shmem;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.util.typedef.internal.U;

/**
 * NOTE: Native library should be loaded, before methods of this class are called.
 * Native library is loaded with: {@link IpcSharedMemoryNativeLoader#load(org.apache.ignite.IgniteLogger)}.
 */
public class IpcSharedMemoryUtils {
    /**
     * Allocates shared memory segment and semaphores for IPC exchange.
     *
     * @param tokFileName OS token file name.
     * @param size Memory space size in bytes.
     * @param debug {@code True} to output debug to stdout (will set global flag).
     * @return Shared memory pointer.
     * @throws IgniteCheckedException If failed.
     */
    static native long allocateSystemResources(String tokFileName, int size, boolean debug)
        throws IgniteCheckedException;

    /**
     * Attaches to previously allocated shared memory segment.
     *
     * @param shmemId OS shared memory segment ID.
     * @param debug {@code True} to output debug to stdout (will set global flag).
     * @return Shared memory pointer.
     * @throws IgniteCheckedException If failed.
     */
    static native long attach(int shmemId, boolean debug) throws IgniteCheckedException;

    /**
     * Stops IPC communication. Call {@link #freeSystemResources(String, long, boolean)} after this call.
     *
     * @param shmemPtr Shared memory pointer.
     */
    static native void ipcClose(long shmemPtr);

    /**
     * Frees system resources.
     *
     * @param tokFileName Token file name.
     * @param shmemPtr Shared memory pointer
     * @param force {@code True} to force close.
     */
    static native void freeSystemResources(String tokFileName, long shmemPtr, boolean force);

    /**
     * Frees system resources.
     *
     * @param tokFileName Token file name.
     * @param size Size.
     */
    static native void freeSystemResources(String tokFileName, int size);

    /**
     * @param shMemPtr Shared memory pointer.
     * @param dest Destination buffer.
     * @param dOff Destination offset.
     * @param size Size.
     * @param timeout Operation timeout.
     * @return Read bytes count.
     * @throws IgniteCheckedException If space has been closed.
     * @throws IpcSharedMemoryOperationTimedoutException If operation times out.
     */
    static native long readSharedMemory(long shMemPtr, byte dest[], long dOff, long size, long timeout)
        throws IgniteCheckedException, IpcSharedMemoryOperationTimedoutException;

    /**
     * @param shmemPtr Shared memory pointer.
     * @return Unread count.
     */
    static native int unreadCount(long shmemPtr);

    /**
     * @param shmemPtr Shared memory pointer.
     * @return Shared memory ID.
     */
    static native int sharedMemoryId(long shmemPtr);

    /**
     * @param shmemPtr Shared memory pointer.
     * @return Semaphore set ID.
     */
    static native int semaphoreId(long shmemPtr);

    /**
     * @param shMemPtr Shared memory pointer
     * @param dest Destination buffer.
     * @param dOff Destination offset.
     * @param size Size.
     * @param timeout Operation timeout.
     * @return Read bytes count.
     * @throws IgniteCheckedException If space has been closed.
     * @throws IpcSharedMemoryOperationTimedoutException If operation times out.
     */
    static native long readSharedMemoryByteBuffer(long shMemPtr, ByteBuffer dest, long dOff, long size, long timeout)
        throws IgniteCheckedException, IpcSharedMemoryOperationTimedoutException;

    /**
     * @param shMemPtr Shared memory pointer
     * @param src Source buffer.
     * @param sOff Offset.
     * @param size Size.
     * @param timeout Operation timeout.
     * @throws IgniteCheckedException If space has been closed.
     * @throws IpcSharedMemoryOperationTimedoutException If operation times out.
     */
    static native void writeSharedMemory(long shMemPtr, byte src[], long sOff, long size, long timeout)
        throws IgniteCheckedException, IpcSharedMemoryOperationTimedoutException;

    /**
     * @param shMemPtr Shared memory pointer
     * @param src Source buffer.
     * @param sOff Offset.
     * @param size Size.
     * @param timeout Operation timeout.
     * @throws IgniteCheckedException If space has been closed.
     * @throws IpcSharedMemoryOperationTimedoutException If operation times out.
     */
    static native void writeSharedMemoryByteBuffer(long shMemPtr, ByteBuffer src, long sOff, long size, long timeout)
        throws IgniteCheckedException, IpcSharedMemoryOperationTimedoutException;

    /** @return PID of the current process (-1 on error). */
    public static int pid() {
        // Should be something like this: 1160@mbp.local
        String name = ManagementFactory.getRuntimeMXBean().getName();

        try {
            int idx = name.indexOf('@');

            return idx > 0 ? Integer.parseInt(name.substring(0, idx)) : -1;
        }
        catch (NumberFormatException ignored) {
            return -1;
        }
    }

    /**
     * @param pid PID to check.
     * @return {@code True} if process with passed ID is alive.
     */
    static native boolean alive(int pid);

    /**
     * Returns shared memory ids for Mac OS and Linux platforms.
     *
     * @return Collection of all shared memory IDs in the system.
     * @throws IOException If failed.
     * @throws InterruptedException If failed.
     * @throws IllegalStateException If current OS is not supported.
     */
    static Collection<Integer> sharedMemoryIds() throws IOException, InterruptedException {
        if (U.isMacOs() || U.isLinux())
            return sharedMemoryIdsOnMacOS();
        else
            throw new IllegalStateException("Current OS is not supported.");
    }

    /**
     * @param e Link error.
     * @return Wrapping grid exception.
     */
    static IgniteCheckedException linkError(UnsatisfiedLinkError e) {
        return new IgniteCheckedException("Linkage error due to possible native library, libigniteshmem.so, " +
            "version mismatch (stop all grid nodes, clean up your '/tmp' folder, and try again).", e);
    }

    /**
     * @return Shared memory IDs.
     * @throws IOException If failed.
     * @throws InterruptedException If failed.
     */
    private static Collection<Integer> sharedMemoryIdsOnMacOS() throws IOException, InterruptedException {
        // IPC status from <running system> as of Mon Jan 21 15:33:54 MSK 2013
        // T     ID     KEY        MODE       OWNER    GROUP
        // Shared Memory:
        // m 327680 0x4702fd26 --rw-rw-rw- yzhdanov    staff

        Process proc = Runtime.getRuntime().exec("ipcs -m");

        BufferedReader rdr = new BufferedReader(new InputStreamReader(proc.getInputStream()));

        Collection<Integer> ret = new ArrayList<>();

        try {
            String line;

            while ((line = rdr.readLine()) != null) {
                if (!line.startsWith(getPlatformDependentLineStartFlag()))
                    continue;

                String[] toks = line.split(" ");

                try {
                    ret.add(Integer.parseInt(toks[1]));
                }
                catch (NumberFormatException ignored) {
                    // No-op (just ignore).
                }
            }

            return ret;
        }
        finally {
            proc.waitFor();
        }
    }

    /** @return Flag for {@code ipcs} utility. */
    private static String getPlatformDependentLineStartFlag() {
        if (U.isMacOs())
            return "m ";
        else if (U.isLinux())
            return "0x";
        else
            throw new IllegalStateException("This OS is not supported.");
    }
}
