/* $Header: https://svn.mpia.de/gulli/TwiceAsNice/branch/unstable/TwiceAsNice/basda/src/Interface/Basda/ServiceWorker.cc 15874 2019-08-14 12:58:35Z briegel $
*/

#include <Basda/ServiceWorker.h>
#include <Basda/ServiceState.h>
#include <Nice/Property/BridgeMemFn.hh>

#include <Nice/Util/Exception.h>
#include <Nice/Application.h>
#include <boost/assign/list_of.hpp>

#define _TRM &Basda::ServiceWorker::terminate
#define _RST &Basda::ServiceWorker::deinit
#define _INI &Basda::ServiceWorker::init
#define _ACT &Basda::ServiceWorker::activate
#define _DEA &Basda::ServiceWorker::deactivate
#define _WRK &Basda::ServiceWorker::work
#define _PCK &Basda::ServiceWorker::packin

#define _EOFF &Basda::ServiceWorker::errorOffline
#define _ESTB &Basda::ServiceWorker::errorStandby
#define _EON &Basda::ServiceWorker::errorOnline
#define _EWRK &Basda::ServiceWorker::errorWorking

#define _OFF &Basda::ServiceWorker::idleOffline
#define _STB &Basda::ServiceWorker::idleStandby
#define _ON  &Basda::ServiceWorker::idleOnline
//#define _FAIL &Basda::ServiceWorker::idleFail


#undef LOG_NAME
#define LOG_NAME m_svc->name()

/*!*****************************************
* @brief the main ICE servicing loop.
*  Executes a preinit() step and enters a loop that is terminated when the next state becomes SHUTDOWN.
*/
void Basda::ServiceWorker::WorkerThread::run()
{
    try
    {
        m_svc->preinit();
    }
    catch (const Nice::Exception & ex)
    {
        F_LOG(ex);
        return;
    }

    catch (const IceUtil::Exception& ex)
    {
        F_LOG(ex);
        return;
    }

    catch (const std::exception& ex)
    {
        F_LOG(ex.what());
        return;
    }

    catch (...)
    {
        F_LOG("unknown error");
        if (m_terminated)
            throw;
    }

    Nice::Monitor::Lock lock(m_monitor);
    while (m_stateMachine.now() != SHUTDOWN)
    {
        try
        {
//            U1_LOG("start" << m_stateMachine);
            Basda::ServiceWorker::Branch * b;
            b = &s_stateTable[m_stateMachine.now()][m_stateMachine.next()];

            m_stateMachine.setTransit(b->m_transit);
//            U1_LOG ( "trans " << !b->m_transit << " " << m_stateMachine.next()  << " " << m_stateMachine.next() << " " << b->m_StateNext);
            if (!m_stateMachine.transit())
            {
                //              m_stateMachine.setNext ( b->m_StateNext );
                m_stateMachine.setAction("IDLE", 0);
            }
            m_stateMachine.setNow(b->m_StateNext);
            m_stateMachine.setTransit(b->m_transit);
            m_svc->m_state->set(m_stateMachine);
//            U1_LOG ( "call " << b->m_transit << " " << m_stateMachine.now() << m_stateMachine << " " << b->m_memname);
            if (b->m_member)
            {
                if (m_stateMachine.isSettled()) m_monitor.notifyAll();
                ((*m_svc).*(b->m_member))();
            }
//            U1_LOG("next " << m_stateMachine << " " << b->m_memname);
//            sleep(Nice::Time::seconds(2.0));
        }

        catch (const Nice::Exception & ex)
        {
            E_LOG(ex);
            sleep(Nice::Time::seconds(24.0));
            m_stateMachine.setNext(Basda::ERROR);
        }

        catch (const IceUtil::Exception& ex)
        {
            E_LOG(ex);
            m_stateMachine.setNext(Basda::ERROR);
        }

        catch (const std::exception& ex)
        {
            E_LOG(ex.what());
            m_stateMachine.setNext(Basda::ERROR);
        }

        catch (...)
        {
            if (terminated())
                throw;
            E_LOG("unknown error");
            m_stateMachine.setNext(Basda::ERROR);
        }
    }
}

#undef LOG_NAME
#define LOG_NAME name()

/*!*********************************
* @brief A table which tells which state should go via which intermediate state to a target state.
*  The 6 states are SHUTDOWN, OFFLINE, STANDBY, ONLINE, WORKING and ERROR.
*  The principles are that 
*   * ERROR goes into OFFLINE, 
*   * WORKING is not triggered if OFFLINE or STANDBY.
*   * SHUTDOWN may transvers via OFFLINE and/or STANDBY.
*  
*/
Basda::ServiceWorker::BranchTable Basda::ServiceWorker::s_stateTable =
{
/*  vFROM / To> SHUTDOWN                   OFFLINE                     STANDBY                     ONLINE                     WORKING                       ERROR */
/* SHUTDOWN */{{SHUTDOWN, 0, 0,   "nul"},{SHUTDOWN, 0, 0, "nul" },{ SHUTDOWN, 0, 0, "nul" },{ SHUTDOWN, 0, 0, "nul" },{ SHUTDOWN, 0, 0, "nul" },{ SHUTDOWN, 0, 0, "nul" } },
/* OFFLINE  */{{SHUTDOWN, 0, _TRM,"trm"},{OFFLINE, 0, _OFF, "OFF" },{ STANDBY, 1, _INI, "ini" },{ STANDBY, 1, _INI, "ini" },{ OFFLINE, 0, 0, "nul" },{ ERROR, 1, _EOFF, "err" }},
/* STANDBY  */{{OFFLINE, 1, _RST, "rst"},{OFFLINE, 1, _RST, "rst" },{ STANDBY, 0, _STB, "STB" },{ ONLINE, 1, _ACT, "act" },{ STANDBY, 0, 0, "nul" },{ ERROR, 1, _ESTB, "err" } },
/* ONLINE   */{{STANDBY, 1, _DEA, "dea"},{STANDBY, 1, _DEA, "dea" },{ STANDBY, 1, _DEA, "dea" },{ ONLINE, 0, _ON, "ON" },{ WORKING, 1, _WRK, "wrk" },{ ERROR, 1, _EON, "err" } },
/* WORKING  */{{STANDBY, 1, _DEA, "dea"},{STANDBY, 1, _DEA, "dea" },{ STANDBY, 1, _DEA, "dea" },{ ONLINE, 0, _ON, "ON" },{ WORKING, 1, _PCK, "pck" },{ ERROR, 1, _EWRK, "err" } },
/* ERROR    */{{SHUTDOWN, 0, 0,   "nul"},{OFFLINE, 0, _OFF, "OFF" },{OFFLINE, 0, _OFF, "OFF" },{OFFLINE, 0, _OFF, "OFF" },{OFFLINE, 0, _OFF, "OFF" },{ OFFLINE, 0, _OFF, "OFF" } } };
///* ERROR    */{{SHUTDOWN, 0, 0,   "nul"},{OFFLINE, 0, _OFF, "OFF" },{ STANDBY, 0, _STB, "STB" },{ ONLINE, 0, _ON, "ON" },{ ONLINE, 0, _ON, "ON" },{ OFFLINE, 0, _OFF, "OFF" } } };

/*!************************************
* @brief ctor
* @param _config The property tree of the worker.
*/
Basda::ServiceWorker::ServiceWorker(const Nice::JointPtr & _config) :
    m_config(_config)
{
    D_LOG("construct");
    
    m_worker = new WorkerThread(this);
    
    m_state = m_config->leaf<Basda::ServiceStateMachine> ("STATE");
    m_state->applyBridge(
            new Nice::PropertyBridgeMemFn<Basda::ServiceStateMachine, Basda::ServiceWorker>(this,
                    &Basda::ServiceWorker::triggerState, m_state->id()));

    if (_config->exist("LOAD"))
    {
        m_name = _config->getAtNode<std::map<std::string, std::string> > ("LOAD")["NAME"];
    }
    else
    {
        m_name = "UNKNOWN";
    }
}

/*!************************************
* @brief dtor
*  Does nothing (in this base class)
*/
Basda::ServiceWorker::~ServiceWorker()
{
    D_LOG("destruct");
}

/*!************************************
* @brief change state to a new state
*   If the state is the current state, or a negative state, do nothing.
*   Otherwise change the "next" state in the state machine and notify all threads.
* @param _state the desired target state.
*/
void Basda::ServiceWorker::changeState(Basda::ServiceState _state)
{
    D_LOG("change state " << _state);
    if (_state < 0 || m_worker->stateMachine().now() == _state)
        return;
    
    Nice::Monitor::Lock lock(m_worker->monitor());
    m_worker->stateMachine().setNext(_state);
    m_worker->monitor().notifyAll();
    D_LOG("change state done " << _state);
}

/*!************************************
* @brief change state to a new state
* @param _state the desired target state.
* @param _timeout A timeout for acquiring a lock on the monitor.
* @return true if within the timeout the lock could be acquired and the target state set.
* false if the timeout expired.
*/
bool Basda::ServiceWorker::tryChangeState(Basda::ServiceState _state, const Nice::Time & _timeout)
{
    D_LOG("try change state " << _state);
    if (_state < 0 || m_worker->stateMachine().now() == _state) 
       return true;
    if (_state == Basda::ONLINE && m_worker->stateMachine().now() == Basda::WORKING) 
       return true;
    Nice::Monitor::TryLock lock(m_worker->monitor());
    Nice::Timer timer;
    timer.start(_timeout);
    while (!lock.acquired() && !timer.elapsed())
    {
        sleep(Nice::Time::seconds(0.1));
//        D_LOG("try ... " << _state << " " << timer.now());
        lock.tryAcquire();
    }
    if (lock.acquired())
    {
        m_worker->stateMachine().setNext(_state);
        m_worker->monitor().notifyAll();
        D_LOG("try change state done " << _state);
        return true;
    }
    //    E_LOG("try change state failed " << _state);
    return false;
}

/*!************************************
* @brief change state to a new state
* @param _state the desired target state.
*
*   The difference to changeState() is that failure to acquire a lock on the monitor
*   does not lead to state change notification.
*/
void Basda::ServiceWorker::triggerState(const Basda::ServiceStateMachine & _state)
{
    D_LOG("try change state " << _state.next());
    Nice::Monitor::TryLock lock(m_worker->monitor());
    m_worker->stateMachine().setNext(_state.next());
    if (lock.acquired())
        m_worker->monitor().notifyAll();
    D_LOG("try change state done " << _state.next());
}

/*!***************************
* @brief wait (indefinitely) until the worker has reached a spcifici state
* @param _state The target state to wait for
*/
void Basda::ServiceWorker::waitForState(Basda::ServiceState _state)
{
    Nice::Monitor::Lock lock(m_worker->monitor());
    while (_state != m_worker->stateMachine().now())
    {
        waitForNewState();
    }
}

/*!***************************
* @brief wait (indefinitely) until the worker has reached a specific state
* @param _state The target state to wait for
* @param _timeout A timeout duration for acquiring a lock on the monitor.
* @return false if the lock wasn't acquired within the timeout.
*/
bool Basda::ServiceWorker::timedWaitForState(Basda::ServiceState _state, const Nice::Time & _timeout)
{
    bool rc = true;
    do
    {
        Nice::Monitor::TryLock lock(m_worker->monitor());
        Nice::Timer timer;
        timer.start(_timeout);
        while (!lock.acquired() && !timer.elapsed())
        {
            sleep(Nice::Time::seconds(0.1));
//            D_LOG("wait " << m_state << ":" << m_worker->stateMachine().transit() << " " << _state << " " << timer.now());
            lock.tryAcquire();
        }
        if (!lock.acquired())
            return false;
        D_LOG("sleep " << m_state << ":" << m_worker->stateMachine().transit() << " " << _state << " " << timer.now());
        rc = m_worker->monitor().timedWait(timer.now());
    } while (_state != m_worker->stateMachine().now() && rc);

    D_LOG("done " << m_worker->stateMachine().transit() << " " << _state);
    return rc;
}

/*!***************************
* @brief wait (indefinitely) until the worker has reached a spcifici state
* @param _from The current state or the state to which we're scheduled to go to.
* @param _to The target state to wait for
* @return false If the current state or scheduled state are not the _from state.
*/
bool Basda::ServiceWorker::changeStateOnlyFromTo(Basda::ServiceState _from, Basda::ServiceState _to)
{
    Nice::Monitor::Lock lock(m_worker->monitor());
    if (m_worker->stateMachine().now() == _from && m_worker->stateMachine().next() == _from)
    {
        m_worker->stateMachine().setNext(_to);
        m_worker->monitor().notifyAll();
        return true;
    }
    else
    {
        W_LOG(
                "Aborted state change due current state=" << m_worker->stateMachine().now() << ", next=" << m_worker->stateMachine().next()
                        << ", to=" << _to);
        return false;
    }
}


/*!******************************
* @brief schedule ONLINE as the next target state.
*/
void Basda::ServiceWorker::packin()
{
    try
    {
        D_LOG(m_name << "--packin-> " << m_worker->stateMachine());
        /*if( ! stateHasChanged() ) */
        m_worker->stateMachine().setNext(Basda::ONLINE);
    }
    catch (const Nice::Exception & ex)
    {
        U6_LOG(ex);
    }

    catch (const IceUtil::Exception& ex)
    {
        U6_LOG(ex);
    }

    catch (const std::exception& ex)
    {
        U6_LOG(ex.what());
    }

    catch (...)
    {
        U6_LOG("Unknown error:");
    }
}


/*!******************************
* @brief schedule EXIT as the next target state.
*/
void Basda::ServiceWorker::terminate()
{
    try
    {
        U6_LOG(m_name << "--terminate-> " << m_worker->stateMachine().now());
        sendStateStatus("EXIT", -1);
    }
    catch (const Nice::Exception & ex)
    {
        U6_LOG(ex);
    }

    catch (const IceUtil::Exception& ex)
    {
        U6_LOG(ex);
    }

    catch (const std::exception& ex)
    {
        U6_LOG(ex.what());
    }

    catch (...)
    {
        U6_LOG("Unknown error:");
    }
}

/*!******************************
* @brief does nothing.
*/
void Basda::ServiceWorker::abort()
{
    W_LOG("Not implemented");
}

/*!******************************
* @brief does nothing.
*/
void Basda::ServiceWorker::stop()
{
    W_LOG("Not implemented");
}


/*!******************************
* @brief set the target state to OFFLINE.
*/
void Basda::ServiceWorker::errorOffline()
{
    U6_LOG(m_name << "--error-> " << m_worker->stateMachine().now());
    setState(Basda::OFFLINE);
}

/*!******************************
* @brief set the target state to OFFLINE.
*/
void Basda::ServiceWorker::errorStandby()
{
    U6_LOG(m_name << "--error-> " << m_worker->stateMachine().now());
    //        sendStateStatus( "ERROR", -1 );
    setState(Basda::OFFLINE);
}

/*!******************************
* @brief set the target state to OFFLINE.
*/
void Basda::ServiceWorker::errorOnline()
{
    U6_LOG(m_name << "--error-> " << m_worker->stateMachine().now());
    //        sendStateStatus( "ERROR", -1 );
    setState(Basda::OFFLINE);
}


/*!******************************
* @brief set the target state to OFFLINE.
*/
void Basda::ServiceWorker::errorWorking()
{
    U6_LOG(m_name << "--error-> " << m_worker->stateMachine().now());
    
    
    //        sendStateStatus( "ERROR", -1 );
    setState(Basda::OFFLINE);
}

/*!******************************
* @brief wait for a state change.
*/
void Basda::ServiceWorker::idleOffline()
{
    D_LOG(" ->idleOffline " << m_state);
    waitForNewState();
    D_LOG("Leaving");
}

/*!******************************
* @brief wait for a state change.
*/
void Basda::ServiceWorker::idleStandby()
{
    D_LOG(" ->idleStandby " << m_state);
    waitForNewState();
    D_LOG("Leaving");
}


/*!******************************
* @brief wait for a state change.
*/
void Basda::ServiceWorker::idleOnline()
{
    D_LOG(" ->idleOnline-> " << m_state);
    waitForNewState();
    D_LOG("Leaving");
}
