#include <iostream>
#include <iomanip>
#include <chrono>
#include <boost/uuid/uuid_io.hpp>
#include <thread>
#include "ble/Bluetooth.h"
#include "SoilMoisture.h"
#include "SensorSample.h"
#include "json.hpp"
#include "apps.h"

// I'm lazy
using namespace std;
using namespace std::chrono;
using namespace trygvis::apps;
using namespace trygvis::bluetooth;
using namespace trygvis::soil_moisture;
namespace po = boost::program_options;
using json = nlohmann::json;

bool loop;
sample_format_type format;
time_point<system_clock> targetTime;
unsigned int sleepTime;
vector<unsigned int> sensors;

void withConnection(BluetoothGatt &gatt) {
    SoilMoisture soilMoisture = SoilMoisture::create(gatt);

    const int sensorCount = soilMoisture.getSensorCount();

    if (sensorCount == 0) {
        throw runtime_error("Sensor count is 0");
    }

    // If the user didn't specify any sensors, add all.
    if (sensors.size() == 0) {
        for (int i = 0; i < sensorCount; i++) {
            sensors.push_back(i);
        }
    }

    auto device = gatt.getDevice().getMac();

    targetTime = system_clock::now();

    do {
        for (auto sensor : sensors) {
            if (sensor >= sensorCount) {
                // Ignore invalid sensors
                continue;
            }

            auto epoch = system_clock::now().time_since_epoch();
            auto timestamp = duration_cast<seconds>(epoch).count();
            uint16_t value = soilMoisture.getValue((uint8_t) sensor);

            if (format == sample_format_type::KEY_VALUE) {
                cout << "device=" << device.str()
                        << ", sensor=" << to_string(sensor)
                        << ", timestamp=" << to_string(timestamp)
                        << ", value=" << (int) value << endl;
            } else if (format == sample_format_type::JSON) {
                json j;
                j["device"] = device.str();
                j["sensor"] = sensor;
                j["timestamp"] = timestamp;
                j["value"] = value;
                cout << j << endl;
            } else if (format == sample_format_type::SQL) {
                cout << "INSERT INTO soil_moisture_sample(device, sensor, timestamp, value) VALUES("
                        << "'" << device.str() << "', "
                        << sensor << ", "
                        << timestamp << ", "
                        << value << ";" << endl;
            }
        }

        targetTime = targetTime + seconds(sleepTime);
        this_thread::sleep_until(targetTime);
    } while (loop);
}

namespace trygvis {
namespace apps {

class sm_get_value : public app {

public:
    void add_options(po::options_description_easy_init &options) override {
        options
                ("help", "produce help message")
                ("device", po::value<string>()->required(), "MAC of device to poll")
                ("sensor", po::value<vector<unsigned int>>(&sensors)->multitoken(), "Sensor to poll, defaults to all")
                ("sleep", po::value<unsigned int>(&sleepTime)->default_value(0),
                        "How long to sleep in seconds between each poll. If not give, it will exit after first poll")
                ("format", po::value<sample_format_type>(&format)->default_value(sample_format_type::KEY_VALUE), "Output format");

    }

    int main(app_execution &execution) override {
        __attribute__((unused))
        BluetoothSystem bluetoothSystem;

        auto desc = execution.desc;
        auto vm = execution.vm;

        try {
            if (!vm.count("device")) {
                cerr << "Missing required option: device" << endl;
                cerr << desc << "\n";
                return EXIT_FAILURE;
            }

            auto MAC = vm["device"].as<string>();

            Mac mac = Mac::parseMac(MAC);

            auto &adapter = getAdapter(0);

            auto &device = adapter.getDevice(mac);

            loop = sleepTime > 0;

            do {
                cout << "Connecting to device: " << device.getMac().str() << endl;

                auto &gatt = device.connectGatt();
                try {
                    withConnection(gatt);
                } catch (runtime_error &e) {
                    cout << "exception: " << e.what() << endl;
                }
                gatt.disconnect();
            } while (loop);

            return EXIT_SUCCESS;
        } catch (std::runtime_error ex) {
            cout << "std::runtime_error: " << ex.what() << endl;
            return EXIT_FAILURE;
        } catch (std::exception ex) {
            cout << "std::exception: " << ex.what() << endl;
            return EXIT_FAILURE;
        }
    }


};

}
}

int main(int argc, char *argv[]) {
    sm_get_value app;
    return trygvis::apps::launch_app(argc, argv, app);
}