/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 */

#include "./follower-stat.hpp"

namespace sh4lt::monitor {

using namespace std::chrono;

const size_t FollowerStat::kMinUpdateTimeInMSec;

FollowerStat::FollowerStat(logger::Logger::ptr log, fs::path path)
    : log_(log),
      path_(std::move(path)),
      bps_fps_update_(system_clock::now()),
      follower_(
          path_.string(),
          [&](void* data, size_t size, const sh4lt_time_info_t* time_info) {
            on_data(data, size, time_info);
          },
          [&](const ShType& shtype) { on_shtype(shtype); },
          [&]() { on_disconnected(); },
          log_) {}

void FollowerStat::on_data(void* data, size_t size, const sh4lt_time_info_t* info) {
  const std::lock_guard<std::mutex> lock(mutex_);
  total_size_ += size;
  size_since_last_update_ += size;
  size_ = size;
  frame_count_since_last_update_ += 1;
  buffer_number_ = info->buffer_number;
  buffer_date_ = info->buffer_date;
  speed_ = info->speed;
  if (-1 != info->buffer_duration) {
    buffer_duration_ = info->buffer_duration;
  }
  if (data_print_.empty()) {
    data_print_ = data_printer_(data, size, dp_num_vals_);
  }
  system_clock_date_ = info->system_clock_date;
}

void FollowerStat::on_shtype(const ShType& shtype) {
  const std::lock_guard<std::mutex> lock(mutex_);
  has_shtype_ = true;
  is_connected_ = true;
  if (shtype_) ++re_up_;
  shtype_ = shtype;
}

void FollowerStat::on_disconnected() {
  const std::lock_guard<std::mutex> lock(mutex_);
  is_connected_ = false;
  size_ = 0;
  buffer_duration_ = 0;
}

auto FollowerStat::shtype() const -> ShType {
  const std::lock_guard<std::mutex> lock(mutex_);
  if (!has_shtype_) return {};
  return shtype_;
}

auto FollowerStat::group() const -> std::string {
  const std::lock_guard<std::mutex> lock(mutex_);
  if (!has_shtype_) return {};
  return shtype_.group();
}

auto FollowerStat::label() const -> std::string {
  const std::lock_guard<std::mutex> lock(mutex_);
  if (!has_shtype_) return {};
  return shtype_.label();
}

auto FollowerStat::id() const -> std::size_t {
  const std::lock_guard<std::mutex> lock(mutex_);
  if (!has_shtype_) return 0;
  return shtype_.id();
}

auto FollowerStat::format_date(int64_t rawdate) -> std::string {
  std::ostringstream timecode;
  auto date = nanoseconds(rawdate);
  auto numdays = duration_cast<days>(date).count();
  if (0 != numdays) timecode << duration_cast<days>(date).count() << "_";
  timecode << std::setfill('0') << std::setprecision(0) << std::setw(2)
           << duration_cast<hours>(date).count() % 24 << ":";
  timecode << std::setfill('0') << std::setprecision(0) << std::setw(2)
           << duration_cast<minutes>(date).count() % 60 << ":";
  timecode << std::setfill('0') << std::setprecision(0) << std::setw(2)
           << duration_cast<seconds>(date).count() % 60 << ".";
  timecode << std::setfill('0') << std::setprecision(0) << std::setw(3)
           << duration_cast<milliseconds>(date).count() % 1000;
  return timecode.str();
}

void FollowerStat::update_fps_bps() {
  auto now = system_clock::now();
  auto delta = now - bps_fps_update_;
  //  do not update if less than 1 sec of data has been collected
  if (delta < milliseconds(kMinUpdateTimeInMSec)) return;
  bps_ = 8 * size_since_last_update_ / (duration_cast<milliseconds>(delta).count() / 1000.f);
  fps_ = frame_count_since_last_update_ /
         static_cast<float>(duration_cast<milliseconds>(delta).count() / 1000.f);
  size_since_last_update_ = 0;
  frame_count_since_last_update_ = 0;
  bps_fps_update_ = now;
}

auto FollowerStat::stats() -> InfoTree::ptr {
  auto info = InfoTree::make();
  auto shtype = shtype_.as_info_tree();
  {
    const std::lock_guard<std::mutex> lock(mutex_);
    update_fps_bps();
    info->graft("shtype", shtype);
    info->vgraft("connected", is_connected_);
    info->vgraft("stats.size_kB", size_ / 1000.f);
    info->vgraft("stats.total_size", total_size_);
    info->vgraft("stats.buffer_number", buffer_number_);
    info->vgraft("stats.buffer_date", buffer_date_);
    info->vgraft("stats.speed", speed_);
    info->vgraft("stats.dur_ms", buffer_duration_ / 1000000.f);
    info->vgraft("stats.system_clock_date", system_clock_date_);
    info->vgraft("stats.timecode", format_date(buffer_date_));
    info->vgraft("stats.Mbps", bps_ / 1000000.f);
    info->vgraft("stats.reup", re_up_);
    info->vgraft("stats.fps", fps_);
    info->vgraft("stats.data", data_print_);
    data_print_.clear();
  }
  return info;
}

auto FollowerStat::data_printer_configuration_ptr() const -> const InfoTree* {
  return data_printer_conf_.get();
}

auto FollowerStat::path() const -> fs::path { return path_; }

auto FollowerStat::has_shtype() const -> bool { return has_shtype_; }

void FollowerStat::set_num_value_to_print(size_t num_val) {
  {
    const std::lock_guard<std::mutex> lock(mutex_);
    dp_num_vals_ = num_val;
  }
  data_printer_conf_->vgraft("num_vals", dp_num_vals_);
}

bool FollowerStat::set_data_printer_configuration(std::string format,
                                                  std::string separator,
                                                  size_t precision,
                                                  size_t min_width) {
  auto data_pr = ::sh4lt::utils::make_data_printer_fun(format, separator, precision, min_width);
  if (!data_pr) {
    log_->warning("Follower stat cannot set value for unknown format %", format);
    return false;
  }
  {
    const std::lock_guard<std::mutex> lock(mutex_);
    data_printer_ = *data_pr;
  }
  data_printer_conf_->vgraft("format", format);
  data_printer_conf_->vgraft("separator", separator);
  data_printer_conf_->vgraft("precision", precision);
  data_printer_conf_->vgraft("min_width", min_width);
  return true;
}

}  // namespace sh4lt::monitor
