//===-- examples/mqtt-client/mqtt-client.cpp --------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file examples/mqtt-client/mqtt-client.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2020
///
/// \brief An example showcasing the basic usage of paho-mqttpp3.
///
//===----------------------------------------------------------------------===//

#include "rosa/config/version.h"

#include "rosa/support/diagnostics.h"
#include "rosa/support/log.h"
#include "rosa/support/terminal_colors.h"

#include "cxxopts/cxxopts.hpp"

// NOTE: MSVC warns about some things in the following header.
ROSA_DISABLE_WARNING_PUSH
ROSA_DISABLE_WARNING_MSVC_C4100
ROSA_DISABLE_WARNING_MSVC_C4201
#include "mqtt/async_client.h"
ROSA_DISABLE_WARNING_POP

#include <chrono>
#include <iostream>
#include <thread>

using namespace rosa;
using namespace rosa::terminal;

/**
 * Local callback & listener class for use with the client connection.
 * This implementation is to receive messages but the mqtt::callback interface
 * allows further actions to be defined.
 */
class MQTTCallback : public virtual mqtt::callback {

  /** Callback for when a message arrives.
   * @param Msg Pointer for the MQTT message
   **/
  void message_arrived(mqtt::const_message_ptr Msg) override {
    std::string Topic = Msg->get_topic();
    std::string Message = Msg->to_string();
    LOG_INFO_STREAM << "[Message @ " << Topic << "] " << Message << std::endl;
  }

public:
  /** Constructor **/
  MQTTCallback(void) noexcept = default;
};

/// Helper function to print an error message in red color to the terminal and
/// exit from the application.
///
/// \note The function never returns as it calles `exit()`.
///
/// \param Error error message
/// \param ExitCode exit code to return from the application
void logErrorAndExit(const std::string &Error, const int ExitCode) {
  LOG_ERROR_STREAM << Color::Red << Error << Color::Default << std::endl;
  exit(ExitCode);
}

int main(int argc, char *argv[]) {

  // std::cout << "Das da?" << std::endl;

  /// Host name of the MQTT server
  std::string ServerHost = "localhost";

  /// Port number of the MQTT server
  uint16_t ServerPort = 1883;

  /// MQTT topic
  std::string Topic = "test";

  /// Message to publish. Subscribe and log messages otherwise.
  std::string Message = "";

  /// How long to receive messages.
  const size_t TimeoutSecs = 60;

  // Handle command-line arguments.
  try {
    cxxopts::Options Options(argv[0],
                             library_string() + " -- PahoMQTT CPP Client Example");
    Options.add_options()(
        "host", "Server host",
        cxxopts::value(ServerHost)->default_value("localhost"))(
        "port", "Server port",
        cxxopts::value(ServerPort)->default_value("1883"))(
        "t,topic", "MQTT topic", cxxopts::value(Topic)->default_value("test"))(
        "m,message",
        "Message to publish (subscribe and log messages otherwise)",
        cxxopts::value(Message))("h,help", "Print usage");

    auto Args = Options.parse(argc, argv);

    if (Args.count("help")) {
      LOG_INFO_STREAM << '\n' << Options.help() << std::endl;
      exit(0);
    }

  } catch (const cxxopts::OptionException &e) {
    logErrorAndExit(e.what(), 1);
  } catch (const std::invalid_argument &e) {
    logErrorAndExit(e.what(), 1);
  }

  try {
    std::stringstream ss;
    ss << "tcp://" << ServerHost << ":" << ServerPort;
    const std::string ServerURI = ss.str();
    LOG_INFO_STREAM << "Initializing for " << ServerURI << std::endl;
    mqtt::async_client Client(ServerURI, "");

    LOG_INFO_STREAM << "Connecting to server" << std::endl;
    Client.connect()->wait();

    if (!Message.empty()) {
      LOG_INFO_STREAM << "Publishing message '" << Message << "' to topic '"
                      << Topic << "'" << std::endl;
      mqtt::topic T(Client, Topic);
      T.publish(Message.c_str())->wait();

    } else {
      LOG_INFO_STREAM << "Receiving messages from topic '" << Topic
                      << "' for a short while..." << std::endl;
      MQTTCallback Callback;
      Client.set_callback(Callback);
      Client.subscribe(Topic, {});

      std::this_thread::sleep_for(std::chrono::seconds(TimeoutSecs));
    }

    LOG_INFO_STREAM << "Disconnecting from the server" << std::endl;
    Client.disconnect()->wait();

  } catch (const mqtt::exception &e) {
    logErrorAndExit(e.what(), 1);
  }

  LOG_INFO_STREAM << "Done." << std::endl;

  return 0;
}
