#pragma once

/**
 * \file
 * \brief DBus-интерфейсы и сериализация типов подсистемы MDB Cashless.
 *
 * Файл содержит:
 * - строковые идентификаторы DBus-интерфейсов, свойств и сигналов,
 * - объявления операторов сериализации/десериализации через QDBusArgument,
 * - поддержку передачи структур через Qt DBus.
 *
 * Используется для взаимодействия:
 * - VMC (Vending Machine Controller),
 * - устройства безналичной оплаты (Reader),
 * - внешних сервисов через DBus.
 *
 * \note Для регистрации типов в системе Qt DBus необходимо вызвать
 *       aqsi::mdb::cashless::registerDbusMetaTypes()
 *       до первого использования типов в DBus-сигналах или свойствах.
 *
 * \note Перед использованием DBus-типов также должна быть выполнена
 *       регистрация базовых Qt meta-типов через
 *       aqsi::mdb::cashless::registerMetaTypes().
 */

#include <QDBusArgument>

#include "global_config.h"

#include "../aqsi-mdb_global.h"
#include "types.h"

#define _TO_STRING(s) #s
#define TO_STRING(s)  _TO_STRING(s)

#define DBUS_PROPERTY_MDB_CASHLESS_VMC_EXPANSION_ENABLE_OPTIONS ExpansionEnableOptions
#define DBUS_PROPERTY_MDB_CASHLESS_VMC_STATE                    State
#define DBUS_PROPERTY_MDB_CASHLESS_VMC_VEND_SESSION             VendSession

#define DBUS_SIGNAL_MDB_CASHLESS_VMC_VEND_RESULT_ADDED VendResultAdded

#define DBUS_SIGNAL_MDB_CASHLESS_READER_JUST_RESET             JustReset
#define DBUS_SIGNAL_MDB_CASHLESS_READER_BEGIN_SESSION          BeginSession
#define DBUS_SIGNAL_MDB_CASHLESS_READER_SESSION_CANCEL_REQUEST SessionCancelRequest
#define DBUS_SIGNAL_VI_MDB_CASHLESS_READER_VEND_APPROVED       VendApproved
#define DBUS_SIGNAL_VI_MDB_CASHLESS_READER_VEND_DENIED         VendDenied

namespace aqsi {
namespace mdb {
namespace cashless {

namespace dbus {

/**
 * \brief DBus-интерфейс исходящих сообщений от VMC.
 *
 * Используется для публикации:
 * - состояния устройства,
 * - текущей сессии продажи,
 * - уведомлений о результатах выдачи.
 */
namespace vmc_out {
inline auto const kInterface{QStringLiteral(VI_DBUS_INTERFACE)};

namespace properties {
inline auto const kExpansionEnableOptions{
    QStringLiteral(TO_STRING(DBUS_PROPERTY_MDB_CASHLESS_VMC_EXPANSION_ENABLE_OPTIONS))};
inline auto const kState{QStringLiteral(TO_STRING(DBUS_PROPERTY_MDB_CASHLESS_VMC_STATE))};
inline auto const kVendSession{QStringLiteral(TO_STRING(DBUS_PROPERTY_MDB_CASHLESS_VMC_VEND_SESSION))};
}  // namespace properties

namespace signal {
inline auto const kVendResultAdded{QStringLiteral(TO_STRING(DBUS_SIGNAL_MDB_CASHLESS_VMC_VEND_RESULT_ADDED))};
}
}  // namespace vmc_out

/**
 * \brief DBus-интерфейс исходящих сообщений от устройства безналичной оплаты.
 *
 * Используется для отправки сигналов в VMC о:
 * - сбросе,
 * - начале сессии,
 * - подтверждении или отказе продажи,
 * - отмене сессии.
 */
namespace cashless_out {
inline auto const kInterface{QStringLiteral(VEND_DBUS_INTERFACE)};

namespace signal {
inline auto const kJustReset{QStringLiteral(TO_STRING(DBUS_SIGNAL_MDB_CASHLESS_READER_JUST_RESET))};
inline auto const kBeginSession{QStringLiteral(TO_STRING(DBUS_SIGNAL_MDB_CASHLESS_READER_BEGIN_SESSION))};
inline auto const kSessionCancelRequest{
    QStringLiteral(TO_STRING(DBUS_SIGNAL_MDB_CASHLESS_READER_SESSION_CANCEL_REQUEST))};
inline auto const kVendApproved{QStringLiteral(TO_STRING(DBUS_SIGNAL_VI_MDB_CASHLESS_READER_VEND_APPROVED))};
inline auto const kVendDenied{QStringLiteral(TO_STRING(DBUS_SIGNAL_VI_MDB_CASHLESS_READER_VEND_DENIED))};
}  // namespace signal
}  // namespace cashless_out
}  // namespace dbus

/**
 * \brief Регистрирует все пользовательские типы,
 * используемые при передаче через Qt DBus.
 *
 * Должна вызываться до первого использования типов
 * в сигналах/слотах или свойствах DBus.
 */
void AQSIMDB_EXPORT registerDbusMetaTypes();
}  // namespace cashless
}  // namespace mdb
}  // namespace aqsi

// ======================= State =======================
AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::State const& state);

AQSIMDB_EXPORT QDBusArgument const& operator>>(QDBusArgument const& argument, aqsi::mdb::cashless::State& state);

// ======================= VendSuccess =======================
AQSIMDB_EXPORT QDBusArgument& operator<<(
    QDBusArgument& argument, aqsi::mdb::cashless::VendSuccess::EnhancedItemNumberInfo const& info);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendSuccess::EnhancedItemNumberInfo& info);

AQSIMDB_EXPORT QDBusArgument& operator<<(
    QDBusArgument& argument, aqsi::mdb::cashless::VendSuccess::BasketDetails const& details);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendSuccess::BasketDetails& details);

AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::VendSuccess const& success);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendSuccess& success);

// ======================= VendFailure =======================

AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::VendFailureReason const& value);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendFailureReason& value);

AQSIMDB_EXPORT QDBusArgument const& operator<<(
    QDBusArgument& argument, aqsi::mdb::cashless::VendFailure::BasketDetails const& details);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendFailure::BasketDetails& details);

AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::VendFailure const& failure);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendFailure& failure);

// ======================= VendRequest =======================
AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::VendRequest const& request);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendRequest& request);

// ======================= VendResult =======================
AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::VendResult const& result);

AQSIMDB_EXPORT QDBusArgument const& operator>>(QDBusArgument const& argument, aqsi::mdb::cashless::VendResult& result);

AQSIMDB_EXPORT QDBusArgument& operator<<(
    QDBusArgument& argument, aqsi::mdb::cashless::VendResultCollection const& result);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendResultCollection& result);

// ======================= ExpansionEnableOptions =======================
AQSIMDB_EXPORT QDBusArgument& operator<<(
    QDBusArgument& argument, aqsi::mdb::cashless::ExpansionEnableOptions const& options);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::ExpansionEnableOptions& options);

// ======================= VendSession =======================
AQSIMDB_EXPORT QDBusArgument& operator<<(QDBusArgument& argument, aqsi::mdb::cashless::VendSession const& vend_session);

AQSIMDB_EXPORT QDBusArgument const& operator>>(
    QDBusArgument const& argument, aqsi::mdb::cashless::VendSession& vend_session);

/**
 * \brief Универсальная сериализация std::optional<T> для DBus.
 *
 * Представляется как массив из 0 или 1 элемента:
 * - пустой массив — значение отсутствует,
 * - массив из одного элемента — значение присутствует.
 *
 * \note Тип T должен быть зарегистрирован в системе meta-типов Qt DBus.
 */
template <typename T>
QDBusArgument&
operator<<(QDBusArgument& argument, std::optional<T> const& opt)
{
    int id = qMetaTypeId<T>();
    argument.beginArray(id);
    if (opt) {
        argument << *opt;
    }
    argument.endArray();
    return argument;
}

/**
 * \brief Универсальная десериализация std::optional<T> из DBus.
 *
 * Ожидается массив из 0 или 1 элемента.
 */
template <typename T>
QDBusArgument const&
operator>>(QDBusArgument const& argument, std::optional<T>& opt)
{
    opt.reset();

    argument.beginArray();

    while (!argument.atEnd()) {
        T value;
        argument >> value;
        opt = value;
    }

    argument.endArray();
    return argument;
}
