/*
 * Copyright 2015-2019 OpenIndex.de.
 *
 * 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 de.openindex.support.customer.utils;

import com.sun.jna.Native;
import de.openindex.support.customer.utils.mac.CoreFoundation;
import de.openindex.support.customer.utils.mac.CoreGraphics;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * MacOS specific functions.
 *
 * @author Andreas Rudolph
 */
public class MacUtils {
    @SuppressWarnings("unused")
    private final static Logger LOGGER = LoggerFactory.getLogger(MacUtils.class);
    private final static CoreFoundation CORE_FOUNDATION = loadCoreFoundation();
    private final static CoreGraphics CORE_GRAPHICS = loadCoreGraphics();

    /**
     * Load Core Foundation framework.
     *
     * @return JNA interface of the Core Foundation framework
     */
    private static CoreFoundation loadCoreFoundation() {
        try {
            //LOGGER.debug("Load Core Foundation framework.");
            return Native.load(CoreFoundation.JNA_LIBRARY_NAME, CoreFoundation.class);
        } catch (UnsatisfiedLinkError ex) {
            LOGGER.warn("Can't load Core Foundation framework.", ex);
            return null;
        }
    }

    /**
     * Load Core Graphics framework.
     *
     * @return JNA interface of the Core Graphics framework
     */
    private static CoreGraphics loadCoreGraphics() {
        try {
            //LOGGER.debug("Load Core Graphics framework.");
            return Native.load(CoreGraphics.JNA_LIBRARY_NAME, CoreGraphics.class);
        } catch (UnsatisfiedLinkError ex) {
            LOGGER.warn("Can't load Core Graphics framework.", ex);
            return null;
        }
    }

    /**
     * Post a keyboard input event.
     *
     * @param character character to print
     * @param keyDown   key is pressed or released
     */
    private static void postKeyboardEvent(final char character, boolean keyDown) {
        if (CORE_GRAPHICS == null || CORE_FOUNDATION == null)
            return;

        final CoreGraphics.CGEventRef event = CORE_GRAPHICS.CGEventCreateKeyboardEvent(
                null, (char) 0, keyDown);

        CORE_GRAPHICS.CGEventKeyboardSetUnicodeString(event, 1, new char[]{character});
        CORE_GRAPHICS.CGEventPost(CoreGraphics.CGEventTapLocation.kCGHIDEventTap, event);
        CORE_FOUNDATION.CFRelease(event);
    }

    /**
     * Enter a text on macOS systems.
     *
     * @param text text to enter
     * @return true, if the text was successfully sent
     */
    public static boolean sendText(String text) {
        try {
            sendTextViaCoreGraphics(text);
            return true;
        } catch (MacException ex) {
            LOGGER.error("Can't send text via Core Graphics framework!", ex);
        } catch (Exception | Error ex) {
            LOGGER.error("An unexpected error occurred!", ex);
        }
        return false;
    }

    /**
     * Enter a text by sending input events through the Quartz API via JNA.
     *
     * @param text text to send through the Quartz API
     * @throws MacException if the macOS frameworks are not available
     * @see <a href="https://developer.apple.com/documentation/coregraphics/quartz_event_services">Quartz Event Services</a>
     * @see <a href="https://developer.apple.com/documentation/coregraphics/1456564-cgeventcreatekeyboardevent">CGEventCreateKeyboardEvent</a>
     * @see <a href="https://developer.apple.com/documentation/coregraphics/1456028-cgeventkeyboardsetunicodestring">CGEventKeyboardSetUnicodeString</a>
     * @see <a href="https://developer.apple.com/documentation/coregraphics/1456527-cgeventpost">CGEventPost</a>
     */
    private static void sendTextViaCoreGraphics(String text) throws MacException {
        if (StringUtils.isEmpty(text))
            return;
        if (CORE_FOUNDATION == null)
            throw new MacException("Core Foundation framework was not loaded.");
        if (CORE_GRAPHICS == null)
            throw new MacException("Core Graphics framework was not loaded.");

        for (int i = 0; i < text.length(); i++) {
            final char c = text.charAt(i);
            postKeyboardEvent(c, true);
            postKeyboardEvent(c, false);
        }
    }

    private static class MacException extends Exception {
        private MacException(String message) {
            super(message);
        }

        @SuppressWarnings("unused")
        private MacException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}