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

#include <tango/client/EventQueue.h>

#include <tango/server/Logging.h>

#include <iostream>

namespace
{

template <typename T>
void clear_vector(T &vec)
{
    for(auto elem : vec)
    {
        delete elem;
    }

    vec.clear();
}

} // anonymous namespace

namespace Tango
{

////////////////////////////////////////////////////////////////////////////
// EventQueue class implementation
////////////////////////////////////////////////////////////////////////////

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::EventQueue
//
// description :
//        Two constructors for the EventQueue class. The first one does not take any argument and will allow unlimited
//        event buffering. The second one creates a circular buffer with the maximum size given as argument.
//
// argument :
//        in :
//            - max_size : the maximum buffer size
//
//------------------------------------------------------------------------------------------------------------------

EventQueue::EventQueue()
{
    max_elt = 0;
    insert_elt = 0;
    nb_elt = 0;
}

EventQueue::EventQueue(long max_size)
{
    if(max_size == 0)
    {
        max_elt = 0;
    }
    else
    {
        max_elt = max_size;
    }

    insert_elt = 0;
    nb_elt = 0;
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::EventQueue
//
// description :
//        The class destructor. It frees all the memory allocated to store event data.
//
//-------------------------------------------------------------------------------------------------------------------

EventQueue::~EventQueue()
{
    TANGO_LOG_DEBUG << "Entering EventQueue::~EventQueue nb_elt = " << nb_elt << std::endl;

    //
    // lock the event queue
    //

    omni_mutex_lock l(modification_mutex);

    clear_vector(event_buffer);
    clear_vector(conf_event_buffer);
    clear_vector(ready_event_buffer);
    clear_vector(dev_inter_event_buffer);
    clear_vector(pipe_event_buffer);
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::insert_event
//
// description :
//        Insert a new event in the event queue
//
// argument :
//        in :
//            - new_event : A pointer to the allocated event data structure.
//
//-------------------------------------------------------------------------------------------------------------------

void EventQueue::insert_event(EventData *new_event)
{
    insert_event_helper(new_event, event_buffer);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::insert_event
//
// description :
//        Insert a new attribute configuration event in the event queue
//
// argument :
//        in :
//            - new_event : A pointer to the allocated attribute configuration event data structure.
//
//------------------------------------------------------------------------------------------------------------------

void EventQueue::insert_event(AttrConfEventData *new_event)
{
    insert_event_helper(new_event, conf_event_buffer);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::insert_event
//
// description :
//        Insert a new attribute data ready event in the event queue
//
// argument :
//        in :
//            - new_event : A pointer to the allocated attribute data ready event data structure.
//
//------------------------------------------------------------------------------------------------------------------

void EventQueue::insert_event(DataReadyEventData *new_event)
{
    insert_event_helper(new_event, ready_event_buffer);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::insert_event
//
// description :
//        Insert a new device interface change event in the event queue
//
// argument :
//        in :
//            - new_event : A pointer to the allocated device interface change event data structure.
//
//--------------------------------------------------------------------------------------------------------------------

void EventQueue::insert_event(DevIntrChangeEventData *new_event)
{
    insert_event_helper(new_event, dev_inter_event_buffer);
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::insert_event
//
// description :
//        Insert a new pipe event in the event queue
//
// argument :
//        in :
//            - new_event : A pointer to the allocated pipe event data structure.
//
//-------------------------------------------------------------------------------------------------------------------

void EventQueue::insert_event(PipeEventData *new_event)
{
    insert_event_helper(new_event, pipe_event_buffer);
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::inc_indexes
//
// description :
//        This private method increments the indexes used to acces the queue itself. This is necessary because the queue
//        must be managed as a circular buffer
//
//-------------------------------------------------------------------------------------------------------------------

void EventQueue::inc_indexes()
{
    if(max_elt == 0)
    {
        // unlimited buffer size
        insert_elt++;
        nb_elt++;
    }
    else
    {
        // circular buffer

        insert_elt++;
        if(insert_elt == max_elt)
        {
            insert_elt = 0;
        }

        if(nb_elt != max_elt)
        {
            nb_elt++;
        }
    }
}

//+------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::size
//
// description :
//        Returns the number of events stored in the event queue
//
//-------------------------------------------------------------------------------------------------------------------
int EventQueue::size()
{
    // lock the event queue
    omni_mutex_lock l(modification_mutex);

    return nb_elt;
}

//+-------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::::get_last_event_date
//
// description :
//        Returns the date of the last inserted, and not yet extracted event in the circular buffer
//
//--------------------------------------------------------------------------------------------------------------------

TimeVal EventQueue::get_last_event_date()
{
    TANGO_LOG_DEBUG << "Entering EventQueue::get_last_insert_date" << std::endl;

    // lock the event queue
    omni_mutex_lock l(modification_mutex);

    if(!event_buffer.empty())
    {
        if(insert_elt == 0)
        {
            return event_buffer[max_elt - 1]->get_date();
        }
        else
        {
            return event_buffer[insert_elt - 1]->get_date();
        }
    }
    else
    {
        if(!conf_event_buffer.empty())
        {
            if(insert_elt == 0)
            {
                return conf_event_buffer[max_elt - 1]->get_date();
            }
            else
            {
                return conf_event_buffer[insert_elt - 1]->get_date();
            }
        }
        else
        {
            if(!ready_event_buffer.empty())
            {
                if(insert_elt == 0)
                {
                    return ready_event_buffer[max_elt - 1]->get_date();
                }
                else
                {
                    return ready_event_buffer[insert_elt - 1]->get_date();
                }
            }
            else
            {
                if(!dev_inter_event_buffer.empty())
                {
                    if(insert_elt == 0)
                    {
                        return dev_inter_event_buffer[max_elt - 1]->get_date();
                    }
                    else
                    {
                        return dev_inter_event_buffer[insert_elt - 1]->get_date();
                    }
                }
                else
                {
                    if(!pipe_event_buffer.empty())
                    {
                        if(insert_elt == 0)
                        {
                            return pipe_event_buffer[max_elt - 1]->get_date();
                        }
                        else
                        {
                            return pipe_event_buffer[insert_elt - 1]->get_date();
                        }
                    }

                    TangoSys_OMemStream o;
                    o << "No new events available!\n";
                    o << "Cannot return any event date" << std::ends;
                    TANGO_THROW_DETAILED_EXCEPTION(EventSystemExcept, API_EventQueues, o.str());
                }
            }
        }
    }
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::get_events
//
// description :
//        Return a vector with all events in the circular buffer. Events are kept in the buffer since the last
//        extraction
//      with get_events(). After returning the event data, the circular buffer gets emptied!
//
// argument :
//        out :
//            - event_list : Reference to the array where events should be stored
//
//------------------------------------------------------------------------------------------------------------------

void EventQueue::get_events(EventDataList &event_list)
{
    get_events_helper(event_list, event_buffer);
}

//--------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::get_events
//
// description :
//        Return a vector with all events in the circular buffer. Events are kept in the buffer since the last
//        extraction
//      with get_events(). After returning the event data, the circular buffer gets emptied!
//
// argument :
//        out :
//            - event_list : Reference to the array where events should be stored
//
//--------------------------------------------------------------------------------------------------------------------

void EventQueue::get_events(AttrConfEventDataList &event_list)
{
    get_events_helper(event_list, conf_event_buffer);
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::get_events
//
// description :
//        Return a vector with all events in the circular buffer. Events are kept in the buffer since the last
//        extraction
//      with get_events(). After returning the event data, the circular buffer gets emptied!
//
// argument :
//        out :
//            - event_list : Reference to the array where events should be stored
//
//--------------------------------------------------------------------------------------------------------------------

void EventQueue::get_events(DataReadyEventDataList &event_list)
{
    get_events_helper(event_list, ready_event_buffer);
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::get_events
//
// description :
//        Return a vector with all events in the circular buffer. Events are kept in the buffer since the last
//        extraction
//      with get_events(). After returning the event data, the circular buffer gets emptied!
//
// argument :
//        out :
//            - event_list : Reference to the array where events should be stored
//
//--------------------------------------------------------------------------------------------------------------------

void EventQueue::get_events(DevIntrChangeEventDataList &event_list)
{
    get_events_helper(event_list, dev_inter_event_buffer);
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::get_events
//
// description :
//        Return a vector with all events in the circular buffer. Events are kept in the buffer since the last
//        extraction
//      with get_events(). After returning the event data, the circular buffer gets emptied!
//
// argument :
//        out :
//            - event_list : Reference to the array where events should be stored
//
//--------------------------------------------------------------------------------------------------------------------

void EventQueue::get_events(PipeEventDataList &event_list)
{
    get_events_helper(event_list, pipe_event_buffer);
}

//-------------------------------------------------------------------------------------------------------------------
//
// method :
//        EventQueue::get_events
//
// description :
//        Call the callback method for all events in the circular buffer. Events are kept in the buffer since the last
//        extraction with get_events(). After returning the event data, the circular buffer gets emptied!
//
// argument :
//        in :
//            - cb : CallBack object ptr
//
//-------------------------------------------------------------------------------------------------------------------

void EventQueue::get_events(CallBack *cb)
{
    TANGO_LOG_DEBUG << "Entering EventQueue::get_events" << std::endl;

    if(cb == nullptr)
    {
        TangoSys_OMemStream o;
        o << "No callback object given!\n";
        o << "Cannot return any event data" << std::ends;
        TANGO_THROW_DETAILED_EXCEPTION(EventSystemExcept, API_EventQueues, o.str());
    }

    //
    // Check the event type
    //

    if(!event_buffer.empty())
    {
        //
        // Get event data for a local data copy. The event reception should not be blocked in case of a problem in the
        // callback method!
        //

        EventDataList event_list;
        get_events(event_list);

        //
        // Loop over all events
        //

        EventDataList::iterator vpos;
        for(vpos = event_list.begin(); vpos != event_list.end(); ++vpos)
        {
            //
            // call the callback method
            //

            try
            {
                cb->push_event(*vpos);
            }
            catch(...)
            {
                std::cerr << "Tango::EventQueue::get_events() exception in callback method \nfor attribute "
                          << (*vpos)->attr_name << "with event type " << (*vpos)->event << std::endl;
            }
        }
    }
    else if(!conf_event_buffer.empty())
    {
        //
        // Get event data for a local data copy. The event reception should not be blocked in case of a problem in the
        // callback method!
        //

        AttrConfEventDataList attr_conf_event_list;
        get_events(attr_conf_event_list);

        //
        // Loop over all events
        //

        AttrConfEventDataList::iterator vpos;
        for(vpos = attr_conf_event_list.begin(); vpos != attr_conf_event_list.end(); ++vpos)
        {
            //
            // call the callback method
            //
            try
            {
                cb->push_event(*vpos);
            }
            catch(...)
            {
                std::cerr << "Tango::EventQueue::get_events() exception in callback method \nfor attribute "
                          << (*vpos)->attr_name << "with event type " << (*vpos)->event << std::endl;
            }
        }
    }
    else if(!dev_inter_event_buffer.empty())
    {
        //
        // Get event data for a local data copy. The event reception should not be blocked in case of a problem in the
        // callback method!
        //

        DevIntrChangeEventDataList dev_intr_event_list;
        get_events(dev_intr_event_list);

        //
        // Loop over all events
        //

        DevIntrChangeEventDataList::iterator vpos;
        for(vpos = dev_intr_event_list.begin(); vpos != dev_intr_event_list.end(); ++vpos)
        {
            //
            // call the callback method
            //
            try
            {
                cb->push_event(*vpos);
            }
            catch(...)
            {
                std::cerr << "Tango::EventQueue::get_events() exception in callback method \nfor device "
                          << (*vpos)->device_name << "with event type " << (*vpos)->event << std::endl;
            }
        }
    }
    else
    {
        //
        // Get event data for a local data copy. The event reception should not be blocked in case of a problem in the
        // callback method!
        //

        DataReadyEventDataList d_ready_event_list;
        get_events(d_ready_event_list);

        //
        // Loop over all events
        //

        DataReadyEventDataList::iterator vpos;
        for(vpos = d_ready_event_list.begin(); vpos != d_ready_event_list.end(); ++vpos)
        {
            //
            // call the callback method
            //
            try
            {
                cb->push_event(*vpos);
            }
            catch(...)
            {
                std::cerr << "Tango::EventQueue::get_events() exception in callback method \nfor attribute "
                          << (*vpos)->attr_name << "with event type " << (*vpos)->event << std::endl;
            }
        }
    }
}

template <typename T, typename U>
void EventQueue::insert_event_helper(T elem, U &vec)
{
    TANGO_LOG_DEBUG << "Entering EventQueue::insert_event" << std::endl;

    // lock the event queue
    omni_mutex_lock l(modification_mutex);

    //
    // Insert data in the event queue
    //

    // when no maximum queue size is given, just add the new event
    if(max_elt == 0)
    {
        vec.push_back(elem);
    }

    // when a maximum size is given, handle a circular buffer
    else
    {
        // allocate ring buffer when not yet done
        if(vec.empty())
        {
            vec.resize(max_elt, nullptr);
        }

        // free data when necessary
        if(vec[insert_elt] != nullptr)
        {
            delete vec[insert_elt];
        }

        // insert the event data pointer into the queue
        vec[insert_elt] = elem;
    }

    // Manage insert and read indexes
    inc_indexes();
}

template <typename T, typename U>
void EventQueue::get_events_helper(T &output, U &vec)
{
    TANGO_LOG_DEBUG << "Entering EventQueue::get_events" << std::endl;

    // lock the event queue
    omni_mutex_lock l(modification_mutex);

    //
    // Set index to read the ring buffer and to initialise the vector
    // of pointers to return.
    // In the returned sequence , indice 0 is the oldest data
    //
    long index = insert_elt;
    if(index == 0)
    {
        index = max_elt;
    }
    index--;

    long seq_index = nb_elt - 1;

    // prepare the vector to be returned

    output.clear();
    output.resize(nb_elt);

    //
    // Read buffer
    //
    for(long i = 0; i < nb_elt; i++)
    {
        output[seq_index] = vec[index];

        // we do not want to free the event data when cleaning-up
        // the vector
        vec[index] = nullptr;

        if(index == 0)
        {
            index = max_elt;
        }
        index--;
        seq_index--;
    }

    // empty the event queue now
    vec.clear();
    insert_elt = 0;
    nb_elt = 0;

    TANGO_LOG_DEBUG << "EventQueue::get_events() : size = " << vec.size() << std::endl;
}

} // namespace Tango
