/*
 * 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.nifi.registry.bootstrap.util;

import java.lang.reflect.Field;

import org.slf4j.Logger;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT;

/**
 * OS specific utilities with generic method interfaces
 */
public final class OSUtils {
    /**
     * @param process NiFi Process Reference
     * @param logger  Logger Reference for Debug
     * @return        Returns pid or null in-case pid could not be determined
     * This method takes {@link Process} and {@link Logger} and returns
     * the platform specific ProcessId for Unix like systems, a.k.a <b>pid</b>
     * In-case it fails to determine the pid, it will return Null.
     * Purpose for the Logger is to log any interaction for debugging.
     */
    private static Long getUnicesPid(final Process process, final Logger logger) {
        try {
            final Class<?> procClass = process.getClass();
            final Field pidField = procClass.getDeclaredField("pid");
            pidField.setAccessible(true);
            final Object pidObject = pidField.get(process);

            logger.debug("PID Object = {}", pidObject);

            if (pidObject instanceof Number) {
                return ((Number) pidObject).longValue();
            }
            return null;
        } catch (final IllegalAccessException | NoSuchFieldException nsfe) {
            logger.debug("Could not find PID for child process due to {}", nsfe);
            return null;
        }
    }

    /**
     * @param process NiFi Registry Process Reference
     * @param logger  Logger Reference for Debug
     * @return        Returns pid or null in-case pid could not be determined
     * This method takes {@link Process} and {@link Logger} and returns
     * the platform specific Handle for Win32 Systems, a.k.a <b>pid</b>
     * In-case it fails to determine the pid, it will return Null.
     * Purpose for the Logger is to log any interaction for debugging.
     */
    private static Long getWindowsProcessId(final Process process, final Logger logger) {
        /* determine the pid on windows plattforms */
        try {
            Field f = process.getClass().getDeclaredField("handle");
            f.setAccessible(true);
            long handl = f.getLong(process);

            Kernel32 kernel = Kernel32.INSTANCE;
            WinNT.HANDLE handle = new WinNT.HANDLE();
            handle.setPointer(Pointer.createConstant(handl));
            int ret = kernel.GetProcessId(handle);
            logger.debug("Detected pid: {}", ret);
            return Long.valueOf(ret);
        } catch (final IllegalAccessException | NoSuchFieldException nsfe) {
            logger.debug("Could not find PID for child process due to {}", nsfe);
        }
        return null;
    }

    /**
     * @param process NiFi Process Reference
     * @param logger  Logger Reference for Debug
     * @return        Returns pid or null in-case pid could not be determined
     * This method takes {@link Process} and {@link Logger} and returns
     * the platform specific ProcessId for Unix like systems or Handle for Win32 Systems, a.k.a <b>pid</b>
     * In-case it fails to determine the pid, it will return Null.
     * Purpose for the Logger is to log any interaction for debugging.
     */
    public static Long getProcessId(final Process process, final Logger logger) {
        if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
            return getUnicesPid(process, logger);
        } else if (process.getClass().getName().equals("java.lang.Win32Process")
                || process.getClass().getName().equals("java.lang.ProcessImpl")) {
            return getWindowsProcessId(process, logger);
        }

        return null;
    }

}