/*
 * Copyright (C) 2018-2020 Western Digital Corporation or its affiliates
 * Copyright (C) 2018 Wearable, Inc.
 *
 * 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
 *
 *      https://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.naniteproject;

/**
 * This class allows communication of event data between the Nanite runtime and
 * Java code. Once an event object is registered for a particular event type,
 * waiting on the event will immediately return if the event is ready. In effect,
 * for each registered event object, the Nanite runtime acts as a separate thread
 * which performs the equivalent of the following:
 *
 * while (true) {
 *   if (pollEvent(type)) {
 *     synchronized (event) {
 *       event.notifyAll()
 *     }
 *   }
 * }
 * 
 * with one important exception: if the monitor for the event is held by some other
 * thread, the event object is not notified. In other words, if one thread is waiting
 * on the event, and another thread enters the monitor for the event, performs some
 * sequence of operations that makes pollEvent() return true, then performs another
 * operation that makes pollEvent() return false (clearing a hardware interrupt, for
 * instance), then releases the monitor, the other thread waiting on the event will
 * not be notified.
 * 
 * Only one object can be registered per event type. If you need to distribute events
 * to multiple threads in your program, create a thread that monitors the event, and
 * distributes work to queues in other threads. The Agent class in
 * org.naniteproject.utils is a good way to accomplish this.
 */
@SuppressWarnings("unused")
public class NaniteEvent {
    private int _type;

    /** Event type for the special system shutdown event */
    public final static int SHUTDOWN = 0;

    /** Event type for platform events */
    public final static int PLATFORM = 1;

    /** Event type for native side-channel events. */
    public final static int SIDE_CHANNEL = 2;

    /** Event type for native io GPIO events. */
    public final static int NATIVE_IO_GPIO = 3;

    /** Event type for native io GPIOTE events. */
    public final static int NATIVE_IO_GPIOTE = 4;

    /** Event type for native io I2C Slave events. */
    public final static int NATIVE_IO_I2C_SLAVE = 5;

    /** Event type for native io I2C Master events. */
    public final static int NATIVE_IO_I2C_MASTER = 6;

    /** Event type for native io SPI Master events. */
    public final static int NATIVE_IO_SPI_MASTER = 7;

    /** Event type for native io USB HID events. */
    public final static int NATIVE_IO_USB = 8;

    /**
     * Register this event with the system.
     * Events must be registered before they are waited on.
     * @return true if the event has been registered; false if not.
     */
    public static native boolean registerEvent(Object event, int type);

    /**
     * Unregister the event with the given type
     * @return true if the event was unregistered; false if not
     */
    public static native boolean unregisterEvent(int type);

    /**
     * Poll the event to see if it is ready.
     * @return true if the event is ready; false if not.
     */
    public static native boolean pollEvent(int type);

    /**
     * Wait for an event to occur or for the specified timeout.
     * @param timeout the timeout in ms, or 0 to wait indefinitely.
     * @return true if ready, false if timed out
     * @throws InterruptedException An exception will be thrown if the thread is interrupted while waiting for an
     * event.
     */
    public synchronized boolean waitEvent(long timeout) throws InterruptedException
    {
        if (pollEvent(_type)) {
            return true;
        }
        wait(timeout);
        return pollEvent(_type);
    }

    /**
     * Create a new event ready for use.
     * @param type The event type.
     * @return The new event object.
     * @throws IllegalStateException if the event could not be registered.
     * @throws IllegalArgumentException if the event type is out of range.
     */
    public NaniteEvent(int type)
    {
        _type = type;
        if (!registerEvent(this, _type)) {
            throw new IllegalStateException("Event type " + _type + " has already been registered.");
        }
    }

    /**
     * Release an event.
     * @throws IllegalStateException if the event was not registered.
     * @throws IllegalArgumentException if the event type is out of range.
     */
    public synchronized void free()
    {
        if (!unregisterEvent(_type)) {
            throw new IllegalStateException("Event type " + _type + " is not currently registered.");
        }
    }
}
