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

#ifndef TANGO_CLIENT_APIEXCEPT_H
#define TANGO_CLIENT_APIEXCEPT_H

#include <tango/common/tango_const.h>
#include <tango/server/Except.h>
#include <tango/server/tango_current_function.h>

#include <string>
#include <vector>

namespace Tango
{

/****************************************************************************
 *
 *        Macro to define API specific exception and their methods
 *
 *****************************************************************************/
class ConnectionFailed : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class CommunicationFailed : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class WrongNameSyntax : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class NonDbDevice : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class WrongData : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class NonSupportedFeature : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class AsynCall : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class AsynReplyNotArrived : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class EventSystemFailed : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class DeviceUnlocked : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

class NotAllowed : public Tango::DevFailed
{
    using DevFailed::DevFailed;
};

template <class Error>
class ApiExcept
{
  public:
    [[noreturn]] static void throw_exception(const char *reason,
                                             const std::string &desc,
                                             const char *origin,
                                             Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].desc = Tango::string_dup(desc.c_str());
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(reason);
        errors[0].origin = Tango::string_dup(origin);
        throw Error(errors);
    }

    [[noreturn]] static void
        throw_exception(const char *reason, char *desc, const char *origin, Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].desc = Tango::string_dup(desc);
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(reason);
        errors[0].origin = Tango::string_dup(origin);
        delete[] desc;
        throw Error(errors);
    }

    [[noreturn]] static void
        throw_exception(const char *reason, const char *desc, const char *origin, Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(1);
        errors.length(1);
        errors[0].desc = Tango::string_dup(desc);
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(reason);
        errors[0].origin = Tango::string_dup(origin);
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(CORBA::SystemException &cex,
                                                const std::string &reason,
                                                const std::string &desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        Tango::Except::the_mutex.lock();
        char *tmp = Tango::Except::print_CORBA_SystemException(&cex);
        errors[0].desc = Tango::string_dup(tmp);
        Tango::Except::the_mutex.unlock();
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin);
        errors[1].desc = Tango::string_dup(desc.c_str());
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason.c_str());
        errors[1].origin = Tango::string_dup(origin);
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(CORBA::SystemException &cex,
                                                char *reason,
                                                char *desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        Tango::Except::the_mutex.lock();
        char *tmp = Tango::Except::print_CORBA_SystemException(&cex);
        errors[0].desc = Tango::string_dup(tmp);
        Tango::Except::the_mutex.unlock();
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin);
        errors[1].desc = Tango::string_dup(desc);
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason);
        errors[1].origin = Tango::string_dup(origin);
        delete[] desc;
        delete[] reason;
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(CORBA::SystemException &cex,
                                                const char *reason,
                                                const std::string &desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        Tango::Except::the_mutex.lock();
        char *tmp = Tango::Except::print_CORBA_SystemException(&cex);
        errors[0].desc = Tango::string_dup(tmp);
        Tango::Except::the_mutex.unlock();
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin);
        errors[1].desc = Tango::string_dup(desc.c_str());
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason);
        errors[1].origin = Tango::string_dup(origin);
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(CORBA::SystemException &cex,
                                                const char *reason,
                                                const std::string &desc,
                                                const std::string &origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        Tango::Except::the_mutex.lock();
        char *tmp = Tango::Except::print_CORBA_SystemException(&cex);
        errors[0].desc = Tango::string_dup(tmp);
        Tango::Except::the_mutex.unlock();
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin.c_str());
        errors[1].desc = Tango::string_dup(desc.c_str());
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason);
        errors[1].origin = Tango::string_dup(origin.c_str());
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(CORBA::SystemException &cex,
                                                const char *reason,
                                                char *desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        Tango::Except::the_mutex.lock();
        char *tmp = Tango::Except::print_CORBA_SystemException(&cex);
        errors[0].desc = Tango::string_dup(tmp);
        Tango::Except::the_mutex.unlock();
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin);
        errors[1].desc = Tango::string_dup(desc);
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason);
        errors[1].origin = Tango::string_dup(origin);
        delete[] desc;
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(
        Error &ex, const char *reason, char *desc, const char *origin, Tango::ErrSeverity sever = Tango::ERR)
    {
        long nb_err = ex.errors.length();
        ex.errors.length(nb_err + 1);
        ex.errors[nb_err].severity = sever;
        ex.errors[nb_err].desc = Tango::string_dup(desc);
        delete[] desc;
        ex.errors[nb_err].origin = Tango::string_dup(origin);
        ex.errors[nb_err].reason = Tango::string_dup(reason);
        throw ex;
    }

    [[noreturn]] static void re_throw_exception(Error &ex,
                                                const char *reason,
                                                const std::string &desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        long nb_err = ex.errors.length();
        ex.errors.length(nb_err + 1);
        ex.errors[nb_err].severity = sever;
        ex.errors[nb_err].desc = Tango::string_dup(desc.c_str());
        ex.errors[nb_err].origin = Tango::string_dup(origin);
        ex.errors[nb_err].reason = Tango::string_dup(reason);
        throw ex;
    }

    [[noreturn]] static void re_throw_exception(
        Tango::DevFailed &ex, const char *reason, char *desc, const char *origin, Tango::ErrSeverity sever = Tango::ERR)
    {
        long nb_err = ex.errors.length();
        ex.errors.length(nb_err + 1);
        ex.errors[nb_err].severity = sever;
        ex.errors[nb_err].desc = Tango::string_dup(desc);
        delete[] desc;
        ex.errors[nb_err].origin = Tango::string_dup(origin);
        ex.errors[nb_err].reason = Tango::string_dup(reason);
        throw ex;
    }

    [[noreturn]] static void re_throw_exception(Tango::DevFailed &ex,
                                                const char *reason,
                                                const std::string &desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        long nb_err = ex.errors.length();
        ex.errors.length(nb_err + 1);
        ex.errors[nb_err].severity = sever;
        ex.errors[nb_err].desc = Tango::string_dup(desc.c_str());
        ex.errors[nb_err].origin = Tango::string_dup(origin);
        ex.errors[nb_err].reason = Tango::string_dup(reason);
        throw ex;
    }

    [[noreturn]] static void re_throw_exception(Tango::DevFailed &ex,
                                                const char *reason,
                                                const char *desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        long nb_err = ex.errors.length();
        ex.errors.length(nb_err + 1);
        ex.errors[nb_err].severity = sever;
        ex.errors[nb_err].desc = Tango::string_dup(desc);
        ex.errors[nb_err].origin = Tango::string_dup(origin);
        ex.errors[nb_err].reason = Tango::string_dup(reason);
        throw ex;
    }

    [[noreturn]] static void re_throw_exception(char *CORBA_error_desc,
                                                const char *reason,
                                                char *desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        errors[0].desc = Tango::string_dup(CORBA_error_desc);
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin);
        errors[1].desc = Tango::string_dup(desc);
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason);
        errors[1].origin = Tango::string_dup(origin);
        delete[] desc;
        throw Error(errors);
    }

    [[noreturn]] static void re_throw_exception(char *CORBA_error_desc,
                                                const char *reason,
                                                const std::string &desc,
                                                const char *origin,
                                                Tango::ErrSeverity sever = Tango::ERR)
    {
        Tango::DevErrorList errors(2);
        errors.length(2);
        errors[0].desc = Tango::string_dup(CORBA_error_desc);
        errors[0].severity = sever;
        errors[0].reason = Tango::string_dup(API_CorbaException);
        errors[0].origin = Tango::string_dup(origin);
        errors[1].desc = Tango::string_dup(desc.c_str());
        errors[1].severity = sever;
        errors[1].reason = Tango::string_dup(reason);
        errors[1].origin = Tango::string_dup(origin);
        throw Error(errors);
    }
};

using ApiConnExcept = ApiExcept<ConnectionFailed>;
using ApiCommExcept = ApiExcept<CommunicationFailed>;
using ApiWrongNameExcept = ApiExcept<WrongNameSyntax>;
using ApiNonDbExcept = ApiExcept<NonDbDevice>;
using ApiDataExcept = ApiExcept<WrongData>;
using ApiNonSuppExcept = ApiExcept<NonSupportedFeature>;
using ApiAsynExcept = ApiExcept<AsynCall>;
using ApiAsynNotThereExcept = ApiExcept<AsynReplyNotArrived>;
using EventSystemExcept = ApiExcept<EventSystemFailed>;
using DeviceUnlockedExcept = ApiExcept<DeviceUnlocked>;
using NotAllowedExcept = ApiExcept<NotAllowed>;

//
// Define macros for the management of the Corba TRANSIENT exception
//

#define TRANSIENT_NOT_EXIST_EXCEPT(E, CLASS, NAME, OBJ)                                                      \
    if(E.minor() == omni::TRANSIENT_CallTimedout)                                                            \
    {                                                                                                        \
        bool need_reconnect = false;                                                                         \
        omniORB::setClientConnectTimeout(NARROW_CLNT_TIMEOUT);                                               \
        try                                                                                                  \
        {                                                                                                    \
            Device_var dev = Device::_duplicate(OBJ->device);                                                \
            dev->ping();                                                                                     \
        }                                                                                                    \
        catch(CORBA::TRANSIENT & trans_ping)                                                                 \
        {                                                                                                    \
            if(trans_ping.minor() == omni::TRANSIENT_ConnectFailed ||                                        \
               trans_ping.minor() == omni::TRANSIENT_CallTimedout ||                                         \
               (OBJ->ext->has_alt_adr == true && trans_ping.minor() == omni::TRANSIENT_CallTimedout))        \
            {                                                                                                \
                need_reconnect = true;                                                                       \
            }                                                                                                \
        }                                                                                                    \
        catch(...)                                                                                           \
        {                                                                                                    \
        }                                                                                                    \
        omniORB::setClientConnectTimeout(0);                                                                 \
                                                                                                             \
        if(need_reconnect == false)                                                                          \
        {                                                                                                    \
            TangoSys_OMemStream desc;                                                                        \
            desc << "Timeout (" << OBJ->timeout << " mS) exceeded on device " << OBJ->dev_name();            \
            desc << std::ends;                                                                               \
            TangoSys_OMemStream ori;                                                                         \
            ori << CLASS << ":" << NAME << std::ends;                                                        \
            ApiCommExcept::re_throw_exception(E, (const char *) API_DeviceTimedOut, desc.str(), ori.str());  \
        }                                                                                                    \
    }                                                                                                        \
                                                                                                             \
    OBJ->set_connection_state(CONNECTION_NOTOK);                                                             \
    ctr++;                                                                                                   \
                                                                                                             \
    if((OBJ->tr_reco == false) || ((ctr == 2) && (OBJ->tr_reco == true)))                                    \
    {                                                                                                        \
        TangoSys_OMemStream desc;                                                                            \
        desc << "Failed to execute " << NAME << " on device " << OBJ->dev_name();                            \
        desc << std::ends;                                                                                   \
        TangoSys_OMemStream ori;                                                                             \
        ori << CLASS << ":" << NAME << std::ends;                                                            \
        ApiCommExcept::re_throw_exception(E, (const char *) API_CommunicationFailed, desc.str(), ori.str()); \
    }

#define TRANSIENT_NOT_EXIST_EXCEPT_CMD(E)                                                                         \
    if(E.minor() == omni::TRANSIENT_CallTimedout)                                                                 \
    {                                                                                                             \
        bool need_reconnect = false;                                                                              \
        try                                                                                                       \
        {                                                                                                         \
            Device_var dev = Device::_duplicate(device);                                                          \
            dev->ping();                                                                                          \
        }                                                                                                         \
        catch(CORBA::TRANSIENT & trans_ping)                                                                      \
        {                                                                                                         \
            if(trans_ping.minor() == omni::TRANSIENT_ConnectFailed ||                                             \
               trans_ping.minor() == omni::TRANSIENT_CallTimedout ||                                              \
               (ext->has_alt_adr == true && trans_ping.minor() == omni::TRANSIENT_CallTimedout))                  \
            {                                                                                                     \
                need_reconnect = true;                                                                            \
            }                                                                                                     \
        }                                                                                                         \
        catch(...)                                                                                                \
        {                                                                                                         \
        }                                                                                                         \
                                                                                                                  \
        if(need_reconnect == false)                                                                               \
        {                                                                                                         \
            TangoSys_OMemStream desc;                                                                             \
            desc << "Timeout (" << timeout << " mS) exceeded on device " << dev_name();                           \
            desc << ", command " << command << std::ends;                                                         \
            ApiCommExcept::re_throw_exception(                                                                    \
                E, (const char *) API_DeviceTimedOut, desc.str(), (const char *) "Connection::command_inout()");  \
        }                                                                                                         \
    }                                                                                                             \
                                                                                                                  \
    set_connection_state(CONNECTION_NOTOK);                                                                       \
    if(get_lock_ctr() != 0)                                                                                       \
    {                                                                                                             \
        set_lock_ctr(0);                                                                                          \
        TangoSys_OMemStream desc;                                                                                 \
        desc << "Device " << dev_name() << " has lost your lock(s) (server re-start?) while executing command "   \
             << command << std::ends;                                                                             \
        DeviceUnlockedExcept::re_throw_exception(                                                                 \
            E, (const char *) DEVICE_UNLOCKED_REASON, desc.str(), (const char *) "Connection::command_inout()");  \
    }                                                                                                             \
    ctr++;                                                                                                        \
                                                                                                                  \
    if((tr_reco == false) || ((ctr == 2) && (tr_reco == true)))                                                   \
    {                                                                                                             \
        TangoSys_OMemStream desc;                                                                                 \
        desc << "Failed to execute command_inout on device " << dev_name();                                       \
        desc << ", command " << command << std::ends;                                                             \
        ApiCommExcept::re_throw_exception(                                                                        \
            E, (const char *) API_CommunicationFailed, desc.str(), (const char *) "Connection::command_inout()"); \
    }

#define TANGO_THROW_DETAILED_EXCEPTION(ExceptionClass, reason, desc) \
    ExceptionClass ::throw_exception(                                \
        reason, desc, (std::string(TANGO_CURRENT_FUNCTION) + " at (" TANGO_FILE_AND_LINE ")").c_str())

#define TANGO_RETHROW_DETAILED_EXCEPTION(ExceptionClass, original, reason, desc) \
    ExceptionClass ::re_throw_exception(                                         \
        original, reason, desc, (std::string(TANGO_CURRENT_FUNCTION) + " at (" TANGO_FILE_AND_LINE ")").c_str())

} // namespace Tango

#endif /* TANGO_CLIENT_APIEXCEPT_H */
