/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2020 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
#pragma once
#include <osgEarth/Export>
#include <vector>
#include <shared_mutex>

// bring in weejobs in the jobs namespace
#define WEEJOBS_EXPORT OSGEARTH_EXPORT
#include <osgEarth/weejobs.h>

namespace osgEarth
{
    /**
    * Pure interface for an object that can be canceled.
    */
    using Cancelable = WEEJOBS_NAMESPACE::cancelable;

    //! Sets the name of the curent thread
    extern OSGEARTH_EXPORT void setThreadName(const std::string& name);

    namespace Threading
    {
        // backwards compatibility typedefs.
        using Mutex = std::mutex;
        using RecursiveMutex = std::recursive_mutex;
        using ScopedMutexLock = std::lock_guard<std::mutex>;
        using ScopedRecursiveMutexLock = std::lock_guard<RecursiveMutex>;

        template<typename T> using Future = typename WEEJOBS_NAMESPACE::future<T>;

        using Event = jobs::detail::event;

        using ReadWriteMutex = std::shared_timed_mutex; // C++14
        using ScopedReadLock = std::shared_lock<ReadWriteMutex>;
        using ScopedWriteLock = std::unique_lock<ReadWriteMutex>;

        /**
        * Mutex that locks on a per-object basis
        */
        template<typename T>
        class Gate
        {
        public:
            Gate() = default;

            //! Lock key's gate
            inline void lock(const T& key)
            {
                std::unique_lock<std::mutex> lock(_m);
                for (;;) {
                    if (emplace(key))
                        return;
                    _block.wait(lock);
                }
            }

            //! Unlock the key's gate
            inline void unlock(const T& key)
            {
                std::unique_lock<std::mutex> lock(_m);
                // only remove first occurance since recursive locks mean the same
                // key/threadid pair can appear multiple times
                for (unsigned i = 0; i < _keys.size(); ++i) {
                    if (_keys[i].first == key) {
                        std::swap(_keys[i], _keys.back());
                        _keys.resize(_keys.size() - 1);
                        break;
                    }
                }
                _block.notify_all();
            }

        private:
            std::mutex _m;
            std::condition_variable_any _block;
            using entry_t = std::pair<T, std::thread::id>;
            std::vector<entry_t> _keys;

            // return true is lock is granted.
            inline bool emplace(const T& key)
            {
                for (auto& k : _keys) {
                    if (k.first == key && k.second != std::this_thread::get_id()) {
                        // fail if the key is already locked by another thread
                        return false;
                    }
                }
                // nb: same key can appear multiple times for the same thread
                _keys.push_back(std::make_pair(key, std::this_thread::get_id()));
                return true;
            }
        };

        //! Gate the locks for the duration of this object's scope
        template<typename T>
        struct ScopedGate
        {
        public:
            //! Lock a gate based on key "key"
            ScopedGate(Gate<T>& gate, const T& key) :
                _gate(gate),
                _key(key),
                _active(true)
            {
                _gate.lock(key);
            }

            //! Lock a gate based on key "key" IFF the predicate is true,
            //! else it's a nop.
            ScopedGate(Gate<T>& gate, const T& key, bool pred) :
                _gate(gate),
                _key(key),
                _active(pred)
            {
                if (_active)
                    _gate.lock(_key);
            }

            //! End-of-scope destructor unlocks the gate
            ~ScopedGate()
            {
                if (_active)
                    _gate.unlock(_key);
            }

        private:
            Gate<T>& _gate;
            T _key;
            bool _active;
        };

        /**
         * Simple convenience construct to make another type "lockable"
         * as long as it has a default constructor
         */
        template<typename T, typename MUTEX = std::mutex>
        struct Mutexed : public T
        {
            Mutexed() : T() { }
            void lock() { _lockable_mutex.lock(); }
            void lock() const { _lockable_mutex.lock(); }
            void unlock() { _lockable_mutex.unlock(); }
            void unlock() const { _lockable_mutex.unlock(); }
            void lock(std::function<void()> func) { lock(); func(); unlock(); }
            void scoped_lock(std::function<void()> func) { lock(); func(); unlock(); }
            MUTEX& mutex() const { return _lockable_mutex; }
            T& operator = (const T& rhs) { return T::operator=(rhs); }
            T& operator = (const T&& rhs) { return T::operator=(rhs); }
        private:
            mutable MUTEX _lockable_mutex;
        };

        template<typename basic_lockable = std::mutex>
        struct scoped_lock_if_base
        {
            scoped_lock_if_base(basic_lockable& lock, bool condition) : _lock(lock), _condition(condition) {
                if (_condition) _lock.lock();
            }
            ~scoped_lock_if_base() {
                if (_condition) _lock.unlock();
            }
            basic_lockable& _lock;
            bool _condition;
        };
        using scoped_lock_if = scoped_lock_if_base<std::mutex>;
    }
}