/*
*
* Made by Berwein
* Login   <berwein@o2kpc>
*
* Started on  Fri Mar 16 16:30:36 2007 Berwein
* $Header: https://svn.mpia.de/gulli/TwiceAsNice/branch/unstable/TwiceAsNice/nice/src/libNice/Nice/App/ConfigClientConnection.cc 16091 2019-10-12 05:42:41Z briegel $
*/

#include <Nice/App/ConfigClient.h>

#include <time.h> 

#include <boost/foreach.hpp>

#include <Nice/Property/Joint.h>
#include <Nice/Time/Time.h>
#include <Nice/Property/Factory.h>
#include <Nice/Property/FactoryFunctor.h>
#include <Nice/App/Adapter.h>
#include <Nice/Property/Joint.hh>
#include <Nice/Property/BridgeOneHitWonderMemFn.hh>
#include <Nice/Application.h>
#include <Nice/Property/ActorLatency.h>
#include <Nice/Property/BridgeMemFn.hh>
#include <Nice/Util/DispatcherThreadPool.hh>
#include <Nice/App/ConfigServiceWatchdog.h>

#include <Nice/Property/BridgeIce.h>
#include <Nice/Property/Joint.h>
#include <Nice/App/Endpoint.h>
#include <Nice/Property/Functor.h>

#include <Nice/App/ConfigServiceWatchdog.h>

#define CONNECTION_MAX_REQUEST 30
#define CONECTION_REQUEST_TIMEOUT 1.4142
#define CONECTION_TIMEOUT_ALLOWED 13
#define CONECTION_REQUEST_WAIT 0.01


namespace Nice
{
namespace Internal
{
    class SavePropertyConnector
    {
        Ice::ObjectAdapterPtr m_adapter;
        Nice::BridgeIceServicePrx m_service;
        Nice::Endpoint m_endpoint;

        size_t m_countTodo;
        size_t m_countDone;
        Nice::Monitor m_monitor;
        Nice::Monitor m_connectMonitor;
        bool m_doneRequest;
        size_t parseForAllNotConnectedNodes(Nice::JointPtr _seed, Nice::LEVEL _level);
    
        void callback();
        void requestNode(const Nice::PropertyPtr & _j, const Nice::Leaf & _leaf);
        bool wait4AllNodes();
        Nice::JointPtr m_seed;
        size_t m_allowedTimeouts;
        

    public:
        SavePropertyConnector(Ice::ObjectAdapterPtr _adapter, const Nice::Endpoint & _ep)
            : m_adapter(_adapter), m_service(0), m_endpoint(_ep), m_countTodo(0), m_countDone(0) {}
        Nice::BridgeIceServicePrx connect(const Nice::JointPtr & _seed);
        Nice::Endpoint endpoint(){ return m_endpoint; }
    };
    
    struct PropertyFunctorJointPair
    {
        Nice::PropertyFunctorPtr m_functor;
        Nice::JointPtr m_joint;
    };
    typedef std::list<PropertyFunctorJointPair> ListPropertyFunctorJointPair;

    struct JointFunctor
    {
        virtual void init(const JointPtr &) = 0;
    };
};
};


void Nice::Internal::SavePropertyConnector::callback()
{
    Nice::Monitor::Lock lock ( m_monitor );
    m_countDone++;
    m_monitor.notify();
}

void Nice::Internal::SavePropertyConnector::requestNode(const Nice::PropertyPtr & _prop, const Nice::Leaf & _leaf)
{
    _prop->applyBridge( new Nice::PropertyBridgeOneHitWonderMemFn<
            Nice::Internal::SavePropertyConnector>(
             this, & Nice::Internal::SavePropertyConnector::callback) );

    Nice::PropertyBridgeIcePtr local = _prop->getBridge<
            Nice::PropertyBridgeIce> ( Nice::PropertyBridgeIce::s_type );
    if ( ! local )
    {
        local = PropertyBridgeIceFactory::create ( _leaf.type, m_adapter );
        _prop->applyBridge ( local,  Nice::PropertyBridgeIce::s_type );
    }
 
    local->connect ( m_service->ice_getConnection(), _leaf.bridge, _leaf.id );
    m_countTodo++;
    if((m_countTodo - m_countDone) < CONNECTION_MAX_REQUEST) return;
    
    Nice::Monitor::Lock lock ( m_monitor );
    m_monitor.timedWait(Nice::Time::seconds(CONECTION_REQUEST_WAIT*(double)(m_countTodo - m_countDone)));
//    U8_LOG("Delayed connection request " << " " << m_countDone << " " << (m_countTodo - m_countDone) << " " << CONECTION_REQUEST_WAIT*(double)(m_countTodo - m_countDone));   
}

size_t Nice::Internal::SavePropertyConnector::parseForAllNotConnectedNodes(Nice::JointPtr _seed, Nice::LEVEL _level)
{
   Nice::JointPtrVector leafs;
   std::string id = "ONE_HIT_WONDER_BRIDGE";
   size_t num = _seed->leafsWithBridgeType<Nice::PropertyBridge>(leafs, id);
   
   for(Nice::JointPtrVector::iterator it = leafs.begin(); it != leafs.end(); it++)
   {
      LOG(_level, (*it)->path());
   }
   return num;
}

bool Nice::Internal::SavePropertyConnector::wait4AllNodes()
{
    m_doneRequest =  true;

    if(m_countDone == m_countTodo) return true;;

    size_t allowedTimeouts = CONECTION_TIMEOUT_ALLOWED;
    size_t lastCountDone = 0;

    do
    {
//        W_LOG(m_countDone << " of " << m_countTodo << " properties successfully connected");
        Nice::Monitor::Lock lock ( m_monitor );
        const bool wokeUp = m_monitor.timedWait(Nice::Time::seconds(CONECTION_REQUEST_TIMEOUT));
        if(m_countDone == m_countTodo) break;
        if(wokeUp)
        {
            if(lastCountDone < m_countDone) 
            {
                lastCountDone = m_countDone;
                allowedTimeouts = CONECTION_TIMEOUT_ALLOWED;
                continue;
            }
        }
        parseForAllNotConnectedNodes(m_seed, Nice::LUSER9);
        W_LOG(m_endpoint.name() << ": timeout " << CONECTION_REQUEST_TIMEOUT << " sec, retries left # " << allowedTimeouts << ", nodes connected: " << m_countDone << " of " <<     m_countTodo);
    }
    while(--allowedTimeouts);

    if(m_countDone != m_countTodo)
    {
        parseForAllNotConnectedNodes(m_seed, Nice::LERROR);
        std::stringstream error;
        error << m_endpoint.name() << ": Only " << m_countDone << " of " << m_countTodo << " properties successfully connected";
        E_LOG(error.str());
        E_THROW(Nice::Exception, error.str());
    }    
    U1_LOG(m_endpoint.name() << ": " << m_countDone << " of " << m_countTodo << " properties successfully connected");
    return true;
}

Nice::BridgeIceServicePrx 
Nice::Internal::SavePropertyConnector::connect(const Nice::JointPtr & _seed)
{
    try
    {
//         m_allowedTimeouts = CONECTION_TIMEOUT_ALLOWED;
        
        m_seed = _seed;

        m_service = m_endpoint.resolveConnection<Nice::BridgeIceService>();
        if(!m_service)
        {
            return 0;
        }

        m_service->ice_timeout ( -1 );
        m_service->ice_getConnection()->setAdapter ( Nice::AppConfigClient::adapter() );
        Nice::LeafVec leafs =  m_service->getTree (
                m_endpoint.node(), m_endpoint.relative(), m_endpoint.persistent());

        Nice::JointPtr lastJoint;
        m_countTodo =  m_countDone = 0;
        m_doneRequest = false;
        
        ListPropertyFunctorJointPair functors;
        for ( Nice::LeafVec::iterator it = leafs.begin(); it != leafs.end(); it++ )
        {
            switch(it->path[0])
            {
            default:
            {
                Nice::PropertyPtr prop;
                lastJoint = _seed->node ( it->path );
                if( !(prop = lastJoint->leaf()))
                {
                    prop = ( Nice::PropertyFactory::create ( it->type ) );
                    if ( !prop ) {
                        W_LOG ( "Property type not available: " <<  it->type );
                        continue;
                    }
                    lastJoint->set ( prop );
                }
                else
                {
//                    U1_LOG("Property exists " << it->path);
                }
                requestNode(prop, *it);
            }
            break;
            case '+':
            {
                if(!lastJoint) {
                    W_LOG("Attribute without property");
                    continue;
                }
                Nice::PropertyPtr prop = lastJoint->leaf();
                Nice::PropertyPtr attr;
                if( !(attr = prop->attribute(&it->path[1])))
                {
                    attr = ( Nice::PropertyFactory::create ( it->type ) );
                    prop->applyAttribute ( &it->path[1], attr );
                }
                requestNode(attr, *it);
                break;
            }
            case '!':
            {
                U3_LOG ( it->path << ":" << it->type );
                break;
            }
            case '&':
            {
                if(!lastJoint) 
                {
                    W_LOG("Functor without Joint");
                    continue;
                }
                if(!lastJoint->children().empty()) 
                {
                    D_LOG("Functor already set");
                    continue;
                }                
                PropertyFunctorPtr functor =
                        Nice::PropertyFunctorFactory::create ( it->type );
                if(!functor)
                {
                    E_LOG("Functor " << it->type << " not found!");
                    continue;
                }
                PropertyFunctorJointPair pair;
                pair.m_functor = functor;
                pair.m_joint = lastJoint;
                functors.push_back(pair);
                break;
            }
            }
        }

        wait4AllNodes();

        for(ListPropertyFunctorJointPair::iterator it = functors.begin();
                it != functors.end(); it++)
        {
            it->m_functor->init(it->m_joint);
        }
    }
    catch (const Ice::ConnectionRefusedException & _e)
    {
//        E_LOG(_e.what());
        return 0;
    }
    catch (const Ice::ConnectionTimeoutException & _e)
    {
        E_LOG(_e.what());
        return 0;
    }
    catch (const std::exception& _e)
    {
        E_LOG(_e.what());
        return 0;
    }
    return m_service;
}



namespace Nice 
{
  namespace Internal 
  {
    struct BridgeSquirrel
    {
          void operator()(const Nice::JointPtr & _joint)
          {
              try
              {
                  Nice::PropertyPtr prop = _joint->leaf();
                  if (Nice::PropertyBridgePtr remoteBridge = prop->getBridge(Nice::PropertyBridgeIce::s_type))
                  {
                      remoteBridge->disconnect();
                      prop->removeBridge(Nice::PropertyBridgeIce::s_type);
                  }

                  for (Nice::PropertyAttrMap::const_iterator jt = _joint->leaf()->attributeMap().begin(); jt
                          != _joint->leaf()->attributeMap().end(); jt++)
                  {
                      Nice::PropertyPtr prop = jt->second;
                      if (Nice::PropertyBridgePtr remoteBridge = prop->getBridge(Nice::PropertyBridgeIce::s_type))
                      {
                          remoteBridge->disconnect();
                          prop->removeBridge(Nice::PropertyBridgeIce::s_type);
                      }
                  }
              }
              catch(const std::exception & _ex)
              {
                E_LOG(_ex.what());
              }
          }
    };
  };
};

#if ICE_INT_VERSION / 100 >= 306

bool Nice::ConfigClientConnection::ReconnectDispatcherFunc::s_terminated = false;
Nice::Monitor Nice::ConfigClientConnection::ReconnectDispatcherFunc::s_monitor;


void Nice::ConfigClientConnection::ReconnectDispatcherFunc::terminate()
{
    Nice::Monitor::Lock lock ( s_monitor );
    s_terminated = true;
    s_monitor.notify();
}

void Nice::ConfigClientConnection::ReconnectDispatcherFunc::operator()(void) const
{
    Nice::Time timeout = (Nice::Application::config()->exist("HEARTBEAT.UPDATE") ?
                          Nice::Application::config()->getAtNode<Nice::Time>("HEARTBEAT.UPDATE") :
                          Nice::Time::seconds(10.0));
    Nice::Monitor::Lock lock ( s_monitor );
    bool rc = s_monitor.timedWait(timeout * ((double)(rand() % 50) / 100.0 + 1.0));
    if(rc || s_terminated) return;
    
     try
     {
         U1_LOG(m_cc->endpoint().name() << ": reconnect start");
         m_cc->reconnect();
         U1_LOG(m_cc->endpoint().name() << ": reconnect notify");
         m_cc->getWatchdog()->notifyAllListeners(true);
         U1_LOG(m_cc->endpoint().name() << ": reconnect success");
     }
     
     catch (const Nice::Exception & _ex)
     {
         W_LOG(m_cc->endpoint().name() << ": reconnect failed");
         Nice::Application::threadpool()->schedule ( Nice::DispatcherFuncPrio ( 0, new ConfigClientConnection::ReconnectDispatcherFunc(m_cc)) );
         return;
     }
}

void Nice::ConfigClientConnection::closed(const Ice::ConnectionPtr&)
{
   if (!m_cc) 
   {
     E_LOG("no parent " << !m_cc);
     return;
   }
   m_cc->getWatchdog()->notifyAllListeners(false);
   Nice::Application::threadpool()->schedule ( Nice::DispatcherFuncPrio ( 0, new ReconnectDispatcherFunc(m_cc)) );
}  

#endif

Nice::BridgeIceServicePrx Nice::ConfigClientConnection::connect (const Nice::Endpoint & _ep, const Nice::JointPtr & _seed, bool _throwIfNotConnected)
{
//    U1_LOG("connecting");
    Nice::Internal::SavePropertyConnector saveConnector(Nice::AppConfigClient::adapter(), _ep);
    Nice::BridgeIceServicePrx service = saveConnector.connect(_seed);
    m_service = service;

    if(! service)
    {
        m_cc->getWatchdog()->notifyAllListeners(false);    
        if(_throwIfNotConnected)
        {
           E_THROW ( Nice::Exception, "Failed connecting to " <<  _ep );
        }
        else
        {
#if ICE_INT_VERSION / 100 >= 306
           Nice::Application::threadpool()->schedule ( Nice::DispatcherFuncPrio ( 0, new ReconnectDispatcherFunc(m_cc)) );
#endif
           return 0;
        }
    }
    
#if ICE_INT_VERSION / 100 >= 306
    m_service->ice_getConnection()->setACM(1, Ice::CloseOff, Ice::HeartbeatOnIdle/*HeartbeatAlways*/);
    m_service->ice_getConnection()->setCallback(this);
#endif
    m_cc->getWatchdog()->notifyAllListeners(true);    
    return service;
}

void Nice::ConfigClientConnection::disconnect (const Nice::JointPtr & _seed)
{
   Internal::BridgeSquirrel squirrel;
   Nice::ForeachLeaf(squirrel, _seed);
#if ICE_INT_VERSION / 100 >= 306
   if(m_service) m_service->ice_getConnection()->setCallback(0);
#endif
   m_service = 0;
}

bool Nice::ConfigClientConnection::isConnected()
{
    if(!m_service) return false;
    try
    {
      m_service->ice_oneway()->ice_ping();
    }
    catch (const Ice::ConnectionLostException & _ex)
    {
//      W_LOG("exception " << _ex.what()); 
      m_service = 0;
      return false;
    }
    catch (const Ice::ConnectionRefusedException & _ex)
    {
//      W_LOG("exception " << _ex.what()); 
      m_service = 0;
      return false;
    }
    catch (const std::exception & _ex)
    {
      W_LOG("unexpected exception " << _ex.what()); 
      m_service = 0;
      return false;
    }
    return true;
}

