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

#include <tango/server/DevIntrThread.h>

#include <tango/server/AutoTangoMonitor.h>
#include <tango/server/Device_5Impl.h>
#include <tango/server/Util.h>
#include <tango/server/ZmqEventSupplier.h>

namespace Tango
{

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DevIntrThread::DevIntrThread
//
// description :
//        The device interface change thread constructor.
//
//-------------------------------------------------------------------------------------------------------------------

DevIntrThread::DevIntrThread(ShDevIntrTh &cmd, TangoMonitor &m, DeviceImpl *_d) :
    shared_data(cmd),
    p_mon(m),
    dev(_d)
{
    shared_data.cmd_pending = false;
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        DevIntrThread::run_undetached
//
// description :
//        The device interface change thread main code
//
//------------------------------------------------------------------------------------------------------------------

void DevIntrThread::run(TANGO_UNUSED(void *ptr))
{
    //
    // The main loop
    //

    bool exit = false;

    try
    {
        while(!exit)
        {
            DevIntrCmdType received = get_command(DEV_INTR_THREAD_SLEEP_TIME);

            switch(received)
            {
            case DEV_INTR_COMMAND:
                execute_cmd();
                break;

            case DEV_INTR_TIME_OUT:
                try
                {
                    push_event();
                }
                catch(Tango::DevFailed &e)
                {
                    std::cerr
                        << "Received a DevFailed exception while trying to push the device interface change event!!!"
                        << std::endl;
                    std::cerr << e.errors[0].desc.in() << std::endl;
                }
                exit = true;
                break;
            }
        }

        {
            omni_mutex_lock sync(p_mon);
            shared_data.th_running = false;
        }
        omni_thread::exit();
    }
    catch(omni_thread_fatal &)
    {
        std::cerr << "OUPS !! A omni thread fatal exception in a device interface change event thread!!!!!!!!"
                  << std::endl;
    }
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        DevIntrThread::get_command
//
// description :
//        This method wait on the shared monitor for a new command to be sent to the device interface change thread.
//        The thread waits with a timeout.
//
// argument :
//        in :
//            - tout : The timeout in mS
//
// return :
//        One enum with two possible value: Thread awaken due to timeout or thread awaken due to a received command
//
//------------------------------------------------------------------------------------------------------------------

DevIntrCmdType DevIntrThread::get_command(DevLong tout)
{
    omni_mutex_lock sync(p_mon);
    DevIntrCmdType ret;

    //
    // Wait on monitor
    //

    if(!shared_data.cmd_pending)
    {
        TANGO_LOG_DEBUG << "DevIntrThread:: Going to wait on monitor" << std::endl;
        p_mon.wait(tout);
    }

    //
    // Test if it is a new command. If yes, copy its data locally
    //

    if(shared_data.cmd_pending)
    {
        local_cmd = shared_data;
        ret = DEV_INTR_COMMAND;
    }
    else
    {
        ret = DEV_INTR_TIME_OUT;
    }

    return ret;
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DevIntrThread::execute_cmd
//
// description :
//        This method is called when a command has been received. It execute the command!
//
//------------------------------------------------------------------------------------------------------------------

void DevIntrThread::execute_cmd()
{
    bool need_exit = false;

    switch(local_cmd.cmd_code)
    {
        //
        // A new rest
        //

    case Tango::DEV_INTR_SLEEP:
    {
        break;
    }

        //
        // Ask locking thread to exit
        //

    case Tango::DEV_INTR_EXIT:
        need_exit = true;
        break;
    }

    //
    // Inform requesting thread that the work is done
    //

    {
        omni_mutex_lock sync(p_mon);
        shared_data.cmd_pending = false;
        p_mon.signal();
    }

    //
    // If the command was an exit one, do it now
    //

    if(need_exit)
    {
        {
            omni_mutex_lock sync(p_mon);
            shared_data.th_running = false;
        }
        omni_thread::exit();
    }
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        DevIntrThread::push_event
//
// description :
//        This method pushes the device interface change event if there is a interface change
//
//------------------------------------------------------------------------------------------------------------------

void DevIntrThread::push_event()
{
    TANGO_LOG_DEBUG << "Device interface change event thread pushing event!" << std::endl;

    AutoTangoMonitor sync(dev, true);

    if(shared_data.iface.has_changed(dev))
    {
        TANGO_LOG_DEBUG << "Device interface has changed" << std::endl;

        Device_5Impl *dev_5 = static_cast<Device_5Impl *>(dev);
        DevCmdInfoList_2 *cmds_list = dev_5->command_list_query_2();

        DevVarStringArray dvsa(1);
        dvsa.length(1);
        dvsa[0] = Tango::string_dup(AllAttr_3);
        AttributeConfigList_5 *atts_list = dev_5->get_attribute_config_5(dvsa);

        ZmqEventSupplier *event_supplier_zmq = Util::instance()->get_zmq_event_supplier();
        event_supplier_zmq->push_dev_intr_change_event(dev, false, cmds_list, atts_list);
    }
}

} // namespace Tango
