#pragma once

#include <array>
#include <vector>

#include <Indoor/math/stats/Statistics.h>


#include <Indoor/data/Timestamp.h>
#include <Indoor/sensors/offline/FileReader.h>
#include <Indoor/sensors/radio/model/LogDistanceModel.h>

#include "Settings.h"

static std::string timeForFilename(const std::time_t& time)
{
    std::stringstream ss;
    ss << std::put_time(std::localtime(&time), "%F_%T"); // ISO 8601 without timezone information.
    auto s = ss.str();
    std::replace(s.begin(), s.end(), ':', '-');
    return s;
}

static std::string currentTimeForFilename()
{
    auto time = std::time(nullptr);
    return timeForFilename(time);
}

static std::filesystem::path appendCurrentTimeToFilename(const std::filesystem::path& fileName)
{
    return fileName.stem().string() + "_" + currentTimeForFilename() + fileName.extension().string();
}

template<typename T>
inline std::filesystem::path appendFileSuffixToPath(const std::filesystem::path& path, T value)
{
    std::string filename = path.stem().string() + "_" + std::to_string(value) + path.extension().string();
    return path.parent_path() / filename;
}


static bool forceDirectories(const std::filesystem::path& path)
{
    if (!std::filesystem::exists(path)) {
        return std::filesystem::create_directories(path);
    }
    return true;
}


template<typename T, int Size>
std::vector<T> asVector(const std::array<T, Size>& src)
{
    std::vector<T> result;
    result.assign(src.begin(), src.end());
    return result;
}

struct WifiMeas {
    Timestamp ts;
    std::array<float, 4> ftmDists;
    std::array<float, 4> rssiDists;

    WifiMeas()
        : ts(Timestamp::fromMS(0)), ftmDists{ NAN, NAN, NAN, NAN }, rssiDists{ NAN, NAN, NAN, NAN }
    { }

    int numSucessMeas() const {
        int count = 0;

        if (!std::isnan(ftmDists[0])) count++;
        if (!std::isnan(ftmDists[1])) count++;
        if (!std::isnan(ftmDists[2])) count++;
        if (!std::isnan(ftmDists[3])) count++;

        return count;
    }

};

template<typename T>
struct CombinedStats {
    Stats::Statistics<T> ftm;
    Stats::Statistics<T> rssi;
};

template<typename T>
void printErrorStats(std::ostream& stream, const CombinedStats<T>& errorStats)
{
    stream << "Walk error:" << "\n";
    stream << "[m]  " << std::setw(10) << "mean" << std::setw(10) << "stdDev" << std::setw(10) << "median" << std::setw(10) << "count" << "\n";

    stream << "FTM  " << std::setw(10) << errorStats.ftm.getAvg()  << std::setw(10) << errorStats.ftm.getStdDev()  << std::setw(10) << errorStats.ftm.getMedian()  << std::setw(10) << errorStats.ftm.getCount() << "\n";
    stream << "RSSI " << std::setw(10) << errorStats.rssi.getAvg() << std::setw(10) << errorStats.rssi.getStdDev() << std::setw(10) << errorStats.rssi.getMedian() << std::setw(10) << errorStats.rssi.getCount() << "\n";
    stream << std::endl;
}

template<typename T>
void printErrorStats(const CombinedStats<T>& errorStats)
{
    return printErrorStats(std::cout, errorStats);
}


static std::vector<WifiMeas> filterOfflineData(const Offline::FileReader& fr)
{
    std::vector<WifiMeas> result;
    WifiMeas currentItem;

    // parse each sensor-value within the offline data
    for (const Offline::Entry& e : fr.getEntries())
    {
        if (e.type != Offline::Sensor::WIFI_FTM) {
            continue;
        }

        // TIME
        const Timestamp ts = Timestamp::fromMS(e.ts);

        // init last ts
        if (currentItem.ts.isZero())
        {
            currentItem.ts = ts;
        }

        // new time step?
        if ((ts - currentItem.ts) > Timestamp::fromMS(10))
        {
            result.push_back(currentItem);
            currentItem = {};
        }

        currentItem.ts = ts;

        // DISTANCE
        auto ftm = fr.getWifiFtm()[e.idx].data;

        const MACAddress& mac = ftm.getAP().getMAC();
        float ftm_offset = Settings::CurrentPath.NUCs.at(mac).ftm_offset;
        float ftmDist = ftm.getFtmDist() + ftm_offset; // in m; plus static offset

        float rssi_pathloss = Settings::CurrentPath.NUCs.at(mac).rssi_pathloss;
        float rssiDist = LogDistanceModel::rssiToDistance(-40, rssi_pathloss, ftm.getRSSI());

        int nucIndex = Settings::nucIndex(mac);
        currentItem.ftmDists[nucIndex] = ftmDist;
        currentItem.rssiDists[nucIndex] = rssiDist;
    }

    return result;
}


struct CmdArguments
{
    std::string executableFilename;
    std::vector<std::string> arguments;

    CmdArguments()
    {
    }

    CmdArguments(int argc, char** argv)
    {
        if (argc > 0)
        {
            executableFilename = std::string(argv[0]);
            for (size_t i = 1; i < argc; i++)
            {
                arguments.push_back(std::string(argv[i]));
            }
        }
    }

    bool hasFlag(const std::string& arg) const
    {
        return std::find(arguments.begin(), arguments.end(), arg) != arguments.end();
    }
};
