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

#include <tango/server/client_addr.h>

#include <tango/common/defines.h>
#include <tango/server/Except.h>
#include <tango/server/Util.h>

// clang-format off
#include <omniORB4/callHandle.h>
#include <omniORB4/omniInterceptors.h>
#include <omniORB4/internal/giopStrand.h>
#include <omniORB4/internal/giopStream.h>
#include <omniORB4/internal/GIOP_S.h>
// clang-format on

#ifdef _TG_WINDOWS_
  #include <sys/types.h>
  #include <ws2tcpip.h>
#else
  #include <arpa/inet.h>
  #include <netdb.h>
  #include <netinet/in.h>
  #include <sys/socket.h>
  #include <sys/time.h>
#endif /* _TG_WINDOWS_ */

#include <cstdio>
#include <iomanip>

namespace Tango
{

// Client call interceptor: works for collocated and remote calls so that the client info is properly setup in any case
// Since the adoption of omniORB 4.3, it is used for both local and remote calls.
void client_call_interceptor(omniCallDescriptor *d, omniServant *s)
{
    // be sure omni_thread::self is defined (crash guarantee otherwise)
    omni_thread::ensure_self t;

    // backup current client info (we might be handling a collocated call generated by a remote one)
    // std::cout << "in BlackBox::client_call_interceptor: backup'ing attached client info (if any)" << std::endl;
    client_addr *previous_client_addr = dynamic_cast<client_addr *>(t.self()->get_value(Util::get_tssk_client_info()));
    if(previous_client_addr != nullptr)
    {
        // std::cout << "in BlackBox::client_call_interceptor: backup'ing attached (making copy)" << std::endl;
        previous_client_addr = new client_addr(*previous_client_addr);
    }

    try
    {
        // -------------------------------------------------------------------------------------------------------
        // the following impl requires omniORB >= 4.3
        // the omniCallDescriptor object has a callHandle() method that returns a potentially null pointer to an
        // omniCallHandle object. In a simple local C++ to C++ call, callHandle() returns null. In a remote call,
        // or a more complex local call (e.g. a call through a POA with a servant locator, or a call from Python
        // to C++), callHandle() returns a valid pointer to a omniCallHandle. Then, in turn, the omniCallHandle
        // has methods that allow you tell remote calls from local ones: accessors for connection details
        // omniCallHandle.connection and omniCallHandle.peeraddress. Both return nullptr in the  case of an
        // in-process call. If omniCallHandle.connection omniCallHandle.peeraddress return non-null, it is a
        // remote call.
        // -------------------------------------------------------------------------------------------------------
        client_addr *a = nullptr;
        omniCallHandle *ch = d->callHandle();
        // collocated c++ to c++ call
        if(ch == nullptr)
        {
            /*
            std::cout << "in BlackBox::client_call_interceptor: handling collocated c++ to c++ call for op: "
                      << d->op()
                      << std::endl;
            */
            // set client info
            a = new client_addr("collocated client (c++ to c++ call)");
        }
        // collocated client (or python to c++) call
        else if(ch->connection() == nullptr || ch->peeraddress() == nullptr)
        {
            /*
            std::cout << "in BlackBox::client_call_interceptor: handling collocated (or python to c++) for op: "
                      << d->op()
                      << std::endl;
            */
            // set client info
            a = new client_addr("collocated client (POA or Python to c++ call)");
        }
        // remote call
        else
        {
            /*
            std::cout << "in BlackBox::client_call_interceptor: handling remote call from "
                      << ch->peeraddress()
                      << " for op: "
                      << d->op()
                      << std::endl;
            */
            // set client info
            a = new client_addr(ch->peeraddress());
        }
        // attach it to he thread handling the call
        // std::cout << "in BlackBox::client_call_interceptor: attaching client info @" << std::hex << a << std::dec
        // << " to the thread" << std::endl;
        t.self()->set_value(Util::get_tssk_client_info(), a);
        // pass on the (i.e. continue) the call (see section 10.3 of the omniORB documentation)
        // std::cout << "in BlackBox::client_call_interceptor: passing on the (i.e. continue) the call..." << std::endl;
        d->interceptedCall(s);
        // restore the previous client info (we might be handling a collocated call generated by a remote one)
        // std::cout << "in BlackBox::client_call_interceptor: restoring initial client info" << std::endl;
        t.self()->set_value(Util::get_tssk_client_info(), previous_client_addr);
        // catch trace context
        // auto ti = Tango::telemetry::Interface::get_current();
        // std::cout << "in BlackBox::client_call_interceptor: current_telemetry_interface is: " << ti->get_id() <<
        // std::endl;
        // std::cout << "in BlackBox::client_call_interceptor: done!" << std::endl;
    }
    catch(...)
    {
        // restore the previous client info (we might be handling a collocated call generated by a remote one)
        // std::cout << "in BlackBox::client_call_interceptor: restoring initial client info (exception context)" <<
        // std::endl;
        t.self()->set_value(Util::get_tssk_client_info(), previous_client_addr);
#ifdef TANGO_USE_TELEMETRY
        // force error status on an dedicated "exit span"
        //--------------------------------------------------------------------------------------------------
        // extract exception info
        // std::string err = Tango::telemetry::Interface::extract_exception_info();
        // catch trace context (see Tango::utils::tss)
        // auto telemetry = Tango::telemetry::Interface::get_current();
        // unfortunately, we can't attach the telemetry context of the caller from this interceptor cause it's
        // called at a early stage of the RPC and almost nothing is accessible. However, the callee attached
        // its telemetry interface to the thread executing this code. We consequently have a change to be
        // sure that any error will be reported to the telemetry service. The last span created is certainly
        // "ended" so we need to create one for our specific need and force the error status on it.
        // TODO: auto span = telemetry->start_span("cppTango::client_call_interceptor");
        // TODO: span->set_status(Tango::telemetry::Span::Status::kError, "exception caught by
        // client_call_interceptor");
#endif
        // propagate the exception
        throw;
    }
}

} // namespace Tango
