#include "statistics.h"

#include <filesystem>
#include <fstream>
#include <iostream>

namespace fs = std::filesystem;
using namespace std::string_literals;

namespace {
 const fs::path statsfilepath{ "/var/lib/webserver/stats.db" };
} // anonymous namespace

void Statistics::load()
{
 std::lock_guard<std::mutex> lock(mMutex);
 std::cout << "Loading statistics..." << std::endl;
 std::ifstream file{statsfilepath, std::ios::in | std::ios::binary};
 if (file.is_open()) {
  Serialization::IArchive archive{file};

  archive >> mBins;
 } else {
  std::cerr << "Warning: Couldn't read statistics" << std::endl;
 }

 mChanged = false;
}

void Statistics::save()
{
 if (mChanged) {
  std::lock_guard<std::mutex> lock(mMutex);
  std::cout << "Saving statistics..." << std::endl;
  std::ofstream file{statsfilepath, std::ios::out | std::ios::binary | std::ios::trunc};
  if (file.is_open()) {
   Serialization::OArchive archive{file};

   archive << mBins;
  } else {
   std::cerr << "Warning: Couldn't write statistics" << std::endl;
  }

  mChanged = false;
 }
}

Statistics::Statistics()
{
 load();
}

Statistics::~Statistics()
{
 save();
}

bool Statistics::Bin::expired() const
{
 uint64_t now {static_cast<uint64_t>(time(nullptr))};

 if (now < start_time)
  std::runtime_error("Statistics time is in the future");

 return start_time + binsize < now;
}

void Statistics::limit()
{
 while (mBins.size() * sizeof(Bin) > maxSize)
  mBins.pop_front(); // discard oldest element
}

void Statistics::count(size_t bytes_in, size_t bytes_out, bool error, bool ipv6, bool https)
{
 std::lock_guard<std::mutex> lock(mMutex);

 mChanged = true;

 if (mBins.empty() || mBins.back().expired()) {
  mBins.emplace_back(Bin{static_cast<uint64_t>((time(nullptr) / binsize) * binsize), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
 }

 Bin& bin{mBins.back()};

 bin.requests++;
 if (error) bin.errors++;
 bin.bytes_in += bytes_in;
 bin.bytes_out += bytes_out;
 
 if (ipv6) {
  bin.requests_ipv6++;
  if (error) bin.errors_ipv6++;
  bin.bytes_in_ipv6 += bytes_in;
  bin.bytes_out_ipv6 += bytes_out;
 }
 
 if (https) {
  bin.requests_https++;
  if (error) bin.errors_https++;
  bin.bytes_in_https += bytes_in;
  bin.bytes_out_https += bytes_out;
 }

 limit();
}

std::string Statistics::getValues()
{
 std::lock_guard<std::mutex> lock(mMutex);

 std::string result;

 for (const auto& bin: mBins) {
  result += std::to_string(bin.start_time) + ","s +

            std::to_string(bin.requests) + ","s +
            std::to_string(bin.errors) + ","s +
            std::to_string(bin.bytes_in) + ","s +
            std::to_string(bin.bytes_out) + ","s +

            std::to_string(bin.requests_ipv6) + ","s +
            std::to_string(bin.errors_ipv6) + ","s +
            std::to_string(bin.bytes_in_ipv6) + ","s +
            std::to_string(bin.bytes_out_ipv6) + ","s +

            std::to_string(bin.requests_https) + ","s +
            std::to_string(bin.errors_https) + ","s +
            std::to_string(bin.bytes_in_https) + ","s +
            std::to_string(bin.bytes_out_https) + "\n"s;
 }

 return result;
}