/*
 * Copyright (C) 2017-2021 Intel Corporation.
 *
 * 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.
 */

#pragma once

#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>

namespace icamera {

typedef std::mutex Mutex;
typedef std::lock_guard<std::mutex> AutoMutex;
typedef std::unique_lock<std::mutex> ConditionLock;

enum {
    PRIORITY_LOWEST = 19,
    PRIORITY_BACKGROUND = 10,
    PRIORITY_NORMAL = 0,
    PRIORITY_FOREGROUND = -2,
    PRIORITY_DISPLAY = -4,
    PRIORITY_URGENT_DISPLAY = -8,
    PRIORITY_AUDIO = -16,
    PRIORITY_URGENT_AUDIO = -19,
    PRIORITY_HIGHEST = -20,
    PRIORITY_DEFAULT = 0,
    PRIORITY_MORE_FAVORABLE = -1,
    PRIORITY_LESS_FAVORABLE = 1,
};

class Condition {
 public:
    Condition() {}
    ~Condition() {}

    /**
     * Wait on the condition variable. MUST be locked with ConditionLock before being called.
     *
     * \param[in] lock: An object of type ConditionLock, which must be locked by the current thread.
     */
    void wait(ConditionLock& lock) { mCondition.wait(lock); }

    /**
     * Wait on the condition variable with a period of time.
     *
     * \param[in] lock: An object of type ConditionLock, which must be locked by the current thread.
     * \param[in] reltime: The maximum time to spend waiting.
     *
     * \return TIMED_OUT if it's not notified to wake up within reltime, otherwise return OK.
     */
    int waitRelative(ConditionLock& lock, int64_t reltime);

    /**
     * Wake up one thread that is waiting on the condition variable.
     */
    void signal() { mCondition.notify_one(); }

    /**
     * Wake up all threads that are waiting on the condition variable.
     */
    void broadcast() { mCondition.notify_all(); }

 private:
    Condition(const Condition& other) = delete;
    Condition& operator=(const Condition&) = delete;

    std::condition_variable mCondition;
};

/**
 * Thread is a wrapper class to std::thread
 *
 * Thread helps manager the thread's state and make the std::thread is easier to used.
 * Thread also hides the platform specific implementation details.
 */
class Thread {
 public:
    Thread();
    virtual ~Thread();

    /**
     * Start the thread.
     */
    virtual int run(std::string name = ("nameless"), int priority = PRIORITY_DEFAULT);

    /**
     * Ask this object's thread to exit. This function is asynchronous, so when it
     * returns the thread might still be running.
     */
    virtual void requestExit();

    /**
     * Wait until this object's thread exits. Returns immediately if not yet running.
     *
     * join will not trigger the thread to exit, it just wait for the thread exits.
     * Do not call it from this object's thread, will return WOULD_BLOCK in that case.
     */
    int join();

    /**
     * Ask this object's thread to exit. This function is synchronous, so when it
     * returns the thread must exit already.
     * It has same effect with calling requestExit and join combined.
     *
     * Do not call from this object's thread, will return WOULD_BLOCK in that case.
     */
    int requestExitAndWait();

    /**
     * Indicates whether this thread is running or not.
     */
    bool isRunning() const;

    /**
     * Indicates whether this thread is going to exit or not.
     */
    bool isExiting() const;

    /**
     * Indicates whether this thread exited or not.
     */
    bool isExited() const;

 private:
    /**
     * threadLoop is the function which is called by the thread.
     * The derived class MUST override this function. The thread starts its life here.
     *
     * There are two ways of using the thread object:
     * 1. loop: threadLoop will be called again as long as it returns true,
     *          and requestExit() wasn't called.
     * 2. once: If threadLoop() returns false, the thread will exit upon return.
     *
     * There are three ways of exiting the thread.
     * 1. threadLoop return false.
     * 2. requestExit is called.
     * 3. requestExitAndWait is called.
     */
    virtual bool threadLoop() { return false; }

 private:
    Thread(const Thread& other) = delete;
    Thread& operator=(const Thread&) = delete;

    /**
     * The function which is used to create the std::thread.
     */
    static void _threadLoop(Thread* self);

    /**
     * Set thread's property such as thread's name or priority.
     */
    void setProperty();

 private:
    enum {
        NOT_STARTED,
        RUNNING,
        EXITING,
        EXITED,
    } mState;

    // The max length for thread name is 15.
    static const int MAX_THREAD_NAME_LEN = 15;
    std::thread* mThread;
    std::string mName;
    std::thread::id mId;
    int mPriority;

    // A lock used to protect internal data and API accessing.
    mutable Mutex mLock;

    // To make sure the thread not dead before "run" not finish.
    Condition mStartCondition;

    // To make sure API like join be able to wait until thread exits.
    Condition mExitedCondition;
};

}  // namespace icamera
