/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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.
 */

#ifndef UTILS_POLL_LOOP_H
#define UTILS_POLL_LOOP_H

#include <utils/Vector.h>
#include <utils/threads.h>

#include <sys/poll.h>

#include <android/looper.h>

struct ALooper : public android::RefBase {
protected:
    virtual ~ALooper() { }

public:
    ALooper() { }
};

namespace android {

/**
 * A basic file descriptor polling loop based on poll() with callbacks.
 */
class PollLoop : public ALooper {
protected:
    virtual ~PollLoop();

public:
    PollLoop(bool allowNonCallbacks);

    /**
     * A callback that it to be invoked when an event occurs on a file descriptor.
     * Specifies the events that were triggered and the user data provided when the
     * callback was set.
     *
     * Returns true if the callback should be kept, false if it should be removed automatically
     * after the callback returns.
     */
    typedef bool (*Callback)(int fd, int events, void* data);

    enum {
        POLL_CALLBACK = ALOOPER_POLL_CALLBACK,
        POLL_TIMEOUT = ALOOPER_POLL_TIMEOUT,
        POLL_ERROR = ALOOPER_POLL_ERROR,
    };
    
    /**
     * Performs a single call to poll() with optional timeout in milliseconds.
     * Invokes callbacks for all file descriptors on which an event occurred.
     *
     * If the timeout is zero, returns immediately without blocking.
     * If the timeout is negative, waits indefinitely until awoken.
     *
     * Returns ALOOPER_POLL_CALLBACK if a callback was invoked.
     *
     * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
     * timeout expired.
     *
     * Returns ALOPER_POLL_ERROR if an error occurred.
     *
     * Returns a value >= 0 containing a file descriptor if it has data
     * and it has no callback function (requiring the caller here to handle it).
     * In this (and only this) case outEvents and outData will contain the poll
     * events and data associated with the fd.
     *
     * This method must only be called on the thread owning the PollLoop.
     * This method blocks until either a file descriptor is signalled, a timeout occurs,
     * or wake() is called.
     * This method does not return until it has finished invoking the appropriate callbacks
     * for all file descriptors that were signalled.
     */
    int32_t pollOnce(int timeoutMillis, int* outEvents = NULL, void** outData = NULL);

    /**
     * Wakes the loop asynchronously.
     *
     * This method can be called on any thread.
     * This method returns immediately.
     */
    void wake();

    /**
     * Control whether this PollLoop instance allows using IDs instead
     * of callbacks.
     */
    bool getAllowNonCallbacks() const;
    
    /**
     * Sets the callback for a file descriptor, replacing the existing one, if any.
     * It is an error to call this method with events == 0 or callback == NULL.
     *
     * Note that a callback can be invoked with the POLLERR, POLLHUP or POLLNVAL events
     * even if it is not explicitly requested when registered.
     *
     * This method can be called on any thread.
     * This method may block briefly if it needs to wake the poll loop.
     */
    void setCallback(int fd, int events, Callback callback, void* data = NULL);

    /**
     * Like setCallback(), but for the NDK callback function.
     */
    void setLooperCallback(int fd, int events, ALooper_callbackFunc* callback,
            void* data);
    
    /**
     * Removes the callback for a file descriptor, if one exists.
     *
     * When this method returns, it is safe to close the file descriptor since the poll loop
     * will no longer have a reference to it.  However, it is possible for the callback to
     * already be running or for it to run one last time if the file descriptor was already
     * signalled.  Calling code is responsible for ensuring that this case is safely handled.
     * For example, if the callback takes care of removing itself during its own execution either
     * by returning false or calling this method, then it can be guaranteed to not be invoked
     * again at any later time unless registered anew.
     *
     * This method can be called on any thread.
     * This method may block briefly if it needs to wake the poll loop.
     *
     * Returns true if a callback was actually removed, false if none was registered.
     */
    bool removeCallback(int fd);

    /**
     * Set the given PollLoop to be associated with the
     * calling thread.  There must be a 1:1 relationship between
     * PollLoop and thread.
     */
    static void setForThread(const sp<PollLoop>& pollLoop);
    
    /**
     * Return the PollLoop associated with the calling thread.
     */
    static sp<PollLoop> getForThread();
    
private:
    struct RequestedCallback {
        Callback callback;
        ALooper_callbackFunc* looperCallback;
        void* data;
    };

    struct PendingCallback {
        int fd;
        int events;
        Callback callback;
        ALooper_callbackFunc* looperCallback;
        void* data;
    };
    
    const bool mAllowNonCallbacks;
    
    Mutex mLock;
    bool mPolling;
    uint32_t mWaiters;
    Condition mAwake;
    Condition mResume;

    int mWakeReadPipeFd;
    int mWakeWritePipeFd;

    Vector<struct pollfd> mRequestedFds;
    Vector<RequestedCallback> mRequestedCallbacks;

    Vector<PendingCallback> mPendingCallbacks; // used privately by pollOnce
    Vector<PendingCallback> mPendingFds;       // used privately by pollOnce
    size_t mPendingFdsPos;
    
    void openWakePipe();
    void closeWakePipe();

    void setCallbackCommon(int fd, int events, Callback callback,
            ALooper_callbackFunc* looperCallback, void* data);
    ssize_t getRequestIndexLocked(int fd);
    void wakeAndLock();
    static void threadDestructor(void *st);
};

} // namespace android

#endif // UTILS_POLL_LOOP_H
