//
// Created by magino on 8. 3. 2020.
//

#ifndef MQTTPUBLISHER_H
#define MQTTPUBLISHER_H

#include <iostream>
#include <cstdlib>
#include <string>
#include <map>
#include <vector>
#include <cstring>
#include "mqtt/client.h"

class sample_mem_persistence: virtual public mqtt::iclient_persistence {
private:
    // Whether the store is open
    bool open_;
    // Use an STL map to store shared persistence pointers against string keys.
    std::map<std::string, std::string> store_;
public:
    sample_mem_persistence(): open_(false) {}

    // "Open" the store
    void open(const std::string& clientId, const std::string& serverURI) override {
        std::cout << "[Opening persistence store for '" << clientId
                  << "' at '" << serverURI << "']" << std::endl;
        open_ = true;
    }

    // Close the persistent store that was previously opened.
    void close() override {
        std::cout << "[Closing persistence store.]" << std::endl;
        open_ = false;
    }

    // Clears persistence, so that it no longer contains any persisted data.
    void clear() override {
        std::cout << "[Clearing persistence store.]" << std::endl;
        store_.clear();
    }

    // Returns whether or not data is persisted using the specified key.
    bool contains_key(const std::string &key) override {
        return store_.find(key) != store_.end();
    }

    // Returns the keys in this persistent data store.
    const mqtt::string_collection& keys() const override {
        static mqtt::string_collection ks;
        ks.clear();
        for (const auto& k : store_)
            ks.push_back(k.first);
        return ks;
    }

    // Puts the specified data into the persistent store.
    void put(const std::string& key, const std::vector<mqtt::string_view>& bufs) override {
        std::cout << "[Persisting data with key '"
                  << key << "']" << std::endl;
        std::string str;
        for (const auto& b : bufs)
            str += b.str();
        store_[key] = std::move(str);
    }

    // Gets the specified data out of the persistent store.
    mqtt::string_view get(const std::string& key) const override {
        std::cout << "[Searching persistence for key '"
                  << key << "']" << std::endl;
        auto p = store_.find(key);
        if (p == store_.end())
            throw mqtt::persistence_exception();
        std::cout << "[Found persistence data for key '"
                  << key << "']" << std::endl;

        return mqtt::string_view(p->second);
    }

    // Remove the data for the specified key.
    void remove(const std::string &key) override {
        std::cout << "[Persistence removing key '" << key << "']" << std::endl;
        auto p = store_.find(key);
        if (p == store_.end())
            throw mqtt::persistence_exception();
        store_.erase(p);
        std::cout << "[Persistence key removed '" << key << "']" << std::endl;
    }
};

class user_callback: public virtual mqtt::callback
{
    void connection_lost(const std::string& cause) override {
        std::cout << "\nConnection lost" << std::endl;
        if (!cause.empty())
            std::cout << "\tcause: " << cause << std::endl;
    }

    void delivery_complete(mqtt::delivery_token_ptr tok) override {
        std::cout << "\n[Delivery complete for token: "
                  << (tok ? tok->get_message_id() : -1) << "]" << std::endl;
    }
};

class MqttPublisher {
private:
    const std::string SERVER_ADDRESS {"tcp://localhost:1883" };
    const std::string REMOTE_SERVER_ADDRESS {"tcp://192.168.0.152:1883" };

    const std::string CLIENT_ID { "recepture-writer" };
    const int QOS = 1;

    sample_mem_persistence _persist;
    mqtt::client _client;
    user_callback _cb;
    mqtt::connect_options _connOpts;
public:
    MqttPublisher(): _client(REMOTE_SERVER_ADDRESS, CLIENT_ID, &_persist) {
        std::cout << "Initialzing mqtt client..." << std::endl;
        _client.set_callback(_cb);

        _connOpts.set_keep_alive_interval(20);
        _connOpts.set_clean_session(true);
        std::cout << "...OK" << std::endl;
    }

    int Connect() {
        try {
            std::cout << "\nConnecting..." << std::endl;
            _client.connect(_connOpts);
            std::cout << "...OK" << std::endl;
        }
        catch (const mqtt::persistence_exception& exc) {
            std::cerr << "Persistence Error: " << exc.what() << " ["
                      << exc.get_reason_code() << "]" << std::endl;
            return 1;
        }
        catch (const mqtt::exception& exc) {
            std::cerr << exc.what() << std::endl;
            return 1;
        }
    }

    int Disconnect() {
        try{
            std::cout << "\nDisconnecting..." << std::endl;
            _client.disconnect();
            std::cout << "...OK" << std::endl << std::endl;
        } catch (const mqtt::exception& exc) {
            std::cerr << exc.what() << std::endl;
            return 1;
        }
    }

    int SendMessage(std::string topic, const std::string& data) {
        try{
            std::cout << "\nSending message..." << std::endl;
            std::cout << data << std::endl;

            auto pubmsg = mqtt::make_message(topic, data);
            pubmsg->set_qos(QOS);
            _client.publish(pubmsg);
            std::cout << "...OK" << std::endl;
        } catch (const mqtt::exception& exc) {
            std::cerr << exc.what() << std::endl;
            return 1;
        }

        return 0;
    }
};

#endif //MQTTPUBLISHER_H