/*
 * Copyright (C) 2019 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 FRAMEWORKS_BASE_COMMONPOOL_H
#define FRAMEWORKS_BASE_COMMONPOOL_H

#include "utils/Macros.h"

#include <log/log.h>

#include <condition_variable>
#include <functional>
#include <future>
#include <mutex>

namespace android {
namespace uirenderer {

template <class T, int SIZE>
class ArrayQueue {
    PREVENT_COPY_AND_ASSIGN(ArrayQueue);
    static_assert(SIZE > 0, "Size must be positive");

public:
    ArrayQueue() = default;
    ~ArrayQueue() = default;

    constexpr size_t capacity() const { return SIZE; }
    constexpr bool hasWork() const { return mHead != mTail; }
    constexpr bool hasSpace() const { return ((mHead + 1) % SIZE) != mTail; }
    constexpr int size() const {
        if (mHead > mTail) {
            return mHead - mTail;
        } else {
            return mTail - mHead + SIZE;
        }
    }

    constexpr void push(T&& t) {
        int newHead = (mHead + 1) % SIZE;
        LOG_ALWAYS_FATAL_IF(newHead == mTail, "no space");

        mBuffer[mHead] = std::move(t);
        mHead = newHead;
    }

    constexpr T pop() {
        LOG_ALWAYS_FATAL_IF(mTail == mHead, "empty");
        int index = mTail;
        mTail = (mTail + 1) % SIZE;
        T ret = std::move(mBuffer[index]);
        mBuffer[index] = nullptr;
        return ret;
    }

private:
    T mBuffer[SIZE];
    int mHead = 0;
    int mTail = 0;
};

class CommonPool {
    PREVENT_COPY_AND_ASSIGN(CommonPool);

public:
    using Task = std::function<void()>;
    static constexpr auto THREAD_COUNT = 2;
    static constexpr auto QUEUE_SIZE = 128;

    static void post(Task&& func);

    template <class F>
    static auto async(F&& func) -> std::future<decltype(func())> {
        typedef std::packaged_task<decltype(func())()> task_t;
        auto task = std::make_shared<task_t>(std::forward<F>(func));
        post([task]() { std::invoke(*task); });
        return task->get_future();
    }

    template <class F>
    static auto runSync(F&& func) -> decltype(func()) {
        std::packaged_task<decltype(func())()> task{std::forward<F>(func)};
        post([&task]() { std::invoke(task); });
        return task.get_future().get();
    };

    // For testing purposes only, blocks until all worker threads are parked.
    static void waitForIdle();

private:
    static CommonPool& instance();

    CommonPool();
    ~CommonPool() {}

    void enqueue(Task&&);
    void doWaitForIdle();

    void workerLoop();

    std::mutex mLock;
    std::condition_variable mCondition;
    int mWaitingThreads = 0;
    ArrayQueue<Task, QUEUE_SIZE> mWorkQueue;
};

}  // namespace uirenderer
}  // namespace android

#endif  // FRAMEWORKS_BASE_COMMONPOOL_H
