/*
 * SPDX-FileCopyrightText: 2004 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#ifndef TANGO_SERVER_TANGOMONITOR_H
#define TANGO_SERVER_TANGOMONITOR_H

#include <tango/server/Except.h>
#include <tango/server/Logging.h>

namespace Tango
{

//--------------------------------------------------------------------------------------------------------------------
//
// class :
//        TangoMonitor
//
// description :
//        This class is used to synchronise device access between polling thread and CORBA request. It is used only for
//        the command_inout and read_attribute calls
//
//--------------------------------------------------------------------------------------------------------------------

class TangoMonitor : public omni_mutex
{
  public:
    TangoMonitor(std::string_view na = "unknown") :
        cond(this),
        name(na)
    {
    }

    ~TangoMonitor() { }

    // Returned by get_locking_thread_id if no thread has the lock.
    static constexpr const int NO_LOCKING_THREAD_ID = -1;

    void get_monitor();
    void rel_monitor();

    void timeout(long new_to)
    {
        _timeout = new_to;
    }

    long timeout()
    {
        return _timeout;
    }

    void wait()
    {
        cond.wait();
    }

    int wait(long);

    void signal()
    {
        cond.signal();
    }

    int get_locking_thread_id();
    long get_locking_ctr();

    std::string &get_name()
    {
        return name;
    }

    void set_name(const std::string &na)
    {
        name = na;
    }

  private:
    long _timeout{DEFAULT_TIMEOUT};
    omni_condition cond;
    omni_thread *locking_thread{};
    long locked_ctr{};
    std::string name;
};

//--------------------------------------------------------------------------------------------------------------------
//
// methods :
//        TangoMonitor::get_locking_thread_id
//        TangoMonitor::get_locking_ctr
//
//-------------------------------------------------------------------------------------------------------------------

inline int TangoMonitor::get_locking_thread_id()
{
    if(locking_thread != nullptr)
    {
        return locking_thread->id();
    }
    else
    {
        return NO_LOCKING_THREAD_ID;
    }
}

inline long TangoMonitor::get_locking_ctr()
{
    omni_mutex_lock guard(*this);
    return locked_ctr;
}

//--------------------------------------------------------------------------------------------------------------------
//
// method :
//        TangoMonitor::get_monitor
//
// description :
//        Get a monitor. The thread will wait (with timeout) if the monitor is already locked. If the thread is already
//        the monitor owner thread, simply increment the locking counter
//
//--------------------------------------------------------------------------------------------------------------------

inline void TangoMonitor::get_monitor()
{
    omni_thread *th = omni_thread::self();

    omni_mutex_lock synchronized(*this);

    TANGO_LOG_DEBUG << "In get_monitor() " << name << ", thread = " << th->id() << ", ctr = " << locked_ctr
                    << std::endl;

    if(locked_ctr == 0)
    {
        locking_thread = th;
    }
    else if(th != locking_thread)
    {
        while(locked_ctr > 0)
        {
            TANGO_LOG_DEBUG << "Thread " << th->id() << ": waiting !!" << std::endl;
            int interupted;

            interupted = wait(_timeout);
            if(interupted == 0)
            {
                TANGO_LOG_DEBUG << "TIME OUT for thread " << th->id() << std::endl;
                std::stringstream ss;
                ss << "Thread " << th->id();
                ss << " is not able to acquire serialization monitor \"" << name << "\", ";
                ss << " it is currently held by thread " << get_locking_thread_id() << ".";
                TANGO_THROW_EXCEPTION(API_CommandTimedOut, ss.str());
            }
        }
        locking_thread = th;
    }
    else
    {
        TANGO_LOG_DEBUG << "owner_thread !!" << std::endl;
    }

    locked_ctr++;
}

//--------------------------------------------------------------------------------------------------------------------
//
// method :
//        TangoMonitor::rel_monitor
//
// description :
//        Release a monitor if the caller thread is the locking thread. Signal other threads if the locking counter
//        goes down to zero
//
//--------------------------------------------------------------------------------------------------------------------

inline void TangoMonitor::rel_monitor()
{
    omni_thread *th = omni_thread::self();
    omni_mutex_lock synchronized(*this);

    TANGO_LOG_DEBUG << "In rel_monitor() " << name << ", ctr = " << locked_ctr << ", thread = " << th->id()
                    << std::endl;
    if((locked_ctr == 0) || (th != locking_thread))
    {
        return;
    }

    locked_ctr--;
    if(locked_ctr == 0)
    {
        TANGO_LOG_DEBUG << "Signalling !" << std::endl;
        locking_thread = nullptr;
        cond.signal();
    }
}

} // namespace Tango

#endif /* TANGO_MONITOR */
