/**
 * SmartCGMS - continuous glucose monitoring and controlling framework
 * https://diabetes.zcu.cz/
 *
 * Copyright (c) since 2018 University of West Bohemia.
 *
 * Contact:
 * diabetes@mail.kiv.zcu.cz
 * Medical Informatics, Department of Computer Science and Engineering
 * Faculty of Applied Sciences, University of West Bohemia
 * Univerzitni 8, 301 00 Pilsen
 * Czech Republic
 * 
 * 
 * Purpose of this software:
 * This software is intended to demonstrate work of the diabetes.zcu.cz research
 * group to other scientists, to complement our published papers. It is strictly
 * prohibited to use this software for diagnosis or treatment of any medical condition,
 * without obtaining all required approvals from respective regulatory bodies.
 *
 * Especially, a diabetic patient is warned that unauthorized use of this software
 * may result into severe injure, including death.
 *
 *
 * Licensing terms:
 * Unless required by applicable law or agreed to in writing, software
 * distributed under these license terms is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * a) For non-profit, academic research, this software is available under the
 *      GPLv3 license.
 * b) For any other use, especially commercial use, you must contact us and
 *       obtain specific terms and conditions for the use of the software.
 * c) When publishing work with results obtained using this software, you agree to cite the following paper:
 *       Tomas Koutny and Martin Ubl, "Parallel software architecture for the next generation of glucose
 *       monitoring", Procedia Computer Science, Volume 141C, pp. 279-286, 2018
 */


/*
 * pattern prediction analysis
 * required signals must be:
 *  1. IG - the reference signal
 *  2. time shifted IG
 *  3. pattern prediction
 *  4. competing signal - e.g.; best bglp 2020
 *
 *  so that we don't get invoked on any other signal.
 *
 */

#include "circular_buffer.h"

constexpr size_t compartment_count = 4;
constexpr bool use_relative_error = true;

const char* stat_file_var_name = "Out_Stat_File";
const char* d1name_file_var_name = "D1NAMO_File";

struct TCustom_Data {
	double last_ig_time = 0.0;
	std::array<TCircular_Buffer<5>, compartment_count> compartments;	
		//keeping just two values should be fine, but let's get fine grained
};

#include <iface/NativeIface.h>
#include <iface/FilterIface.h>
#include <utils/math_utils.cpp>

#include <iostream>
#include <fstream>
#include <vector>
#include <filesystem>
#include <array>

struct TError_Info {
	double ig;
	double time_shift, time_shift_rel;
	double pattern, pattern_rel;
	double competing, competing_rel;
	double device_time;
};


constexpr size_t histogram_count = 24*4;
//constexpr double inv_histogram_granularity = 1.0 / static_cast<double>(histogram_count);
using TDay_Histogram = std::array<double, histogram_count>;

size_t day_idx(const double device_time) {
	double tmp;
	double idx = std::modf(device_time, &tmp)* static_cast<double>(histogram_count);
	return static_cast<size_t>(idx);
}

double rel_err(const double c, const double r) {
	if (use_relative_error) return std::fabs((c - r) / r);
	  else return std::fabs((c - r) );
}


void write_ecdf(std::basic_ostream<char>& stream, const std::string& method, const scgms::TSignal_Stats& stats) {
	stream << std::setprecision(std::numeric_limits<long double>::digits10 + 1);
	stream << method;

	for (size_t i = static_cast<size_t>(scgms::NECDF::min_value); i <= static_cast<size_t>(scgms::NECDF::max_value); i++)
		stream << "; " << stats.ecdf[i];
	
	stream << std::endl;
}

void write_positions(std::basic_ostream<char>& stream, const std::string& method, 
					 std::vector<double> &values, std::vector<double> &thresholds) {
	stream << std::setprecision(std::numeric_limits<long double>::digits10 + 1);
	stream << method << "; 0; ";

	size_t th_idx = 0;
	if (values.size() > 0) {

		std::sort(values.begin(), values.end());
		std::sort(thresholds.begin(), thresholds.end());


		while (th_idx < thresholds.size() && (values[0]>thresholds[th_idx])) {
			stream << "0; ";
			th_idx++;
		}

		const double inv_size = 1.0 / static_cast<double>(values.size());

		for (size_t val_idx = 0; val_idx < values.size(); val_idx++) {
			
			while ((th_idx<thresholds.size()) && (values[val_idx] > thresholds[th_idx])) {
				th_idx++;
				
				stream << static_cast<double>(val_idx+1) * inv_size << "1; ";
			}			
		}
				
	}

	while (th_idx < thresholds.size()) {
		stream << "1; ";
		th_idx++;
	}

	stream << '1' << std::endl;
}

std::filesystem::path resolve_file_path_from_var(const char *var_name) {
	std::filesystem::path result;

	std::vector<char> output;
	std::array<char, 256> stats_filepath;
	size_t dummy_len;
	if (getenv_s(&dummy_len, stats_filepath.data(), stats_filepath.size(), var_name) == 0) {

		result = stats_filepath.data();
	}

	return result;
}

void execute([[maybe_unused]] GUID& sig_id, [[maybe_unused]] double& device_time, [[maybe_unused]] double& level,
	HRESULT& rc, TNative_Environment& environment, [[maybe_unused]] const void* context) {

	//push the value
	environment.custom_data->compartments[environment.current_signal_index].push(device_time, level);
	if (environment.current_signal_index == 0) {
		environment.custom_data->last_ig_time = device_time;
	}

	TError_Info info;

	//and try to get all the levels we need to compute the relative errors
	const double last_ig_time = info.device_time = environment.custom_data->last_ig_time;
	info.ig = environment.custom_data->compartments[0].level(last_ig_time, scgms::apxNo_Derivation);
	info.time_shift = environment.custom_data->compartments[1].level(last_ig_time, scgms::apxNo_Derivation);
	info.pattern = environment.custom_data->compartments[2].level(last_ig_time, scgms::apxNo_Derivation);
	info.competing = environment.custom_data->compartments[3].level(last_ig_time, scgms::apxNo_Derivation);

	if (!Is_Any_NaN(info.ig, info.time_shift, info.pattern, info.competing) && (info.ig!= 0.0)) {

		const auto stats_filepath = resolve_file_path_from_var(stat_file_var_name);

		if (!stats_filepath.empty()) {


			std::fstream stats{ stats_filepath, std::ios::in | std::ios::out | std::ios::app | std::ios::binary };
			//let's write binary data to preserve maximum precision
			stats.write(reinterpret_cast<char*>(&info), sizeof(info));
		}	
	}
}


extern "C" DLL_EXPORT void APIENTRY analyze() {
	auto stats_filepath = resolve_file_path_from_var(stat_file_var_name);

	if (!stats_filepath.empty()) {

		std::fstream stats{ stats_filepath, std::ios::in | std::ios::out | std::ios::binary };

		std::vector<TError_Info> data;
		std::vector<double> shift_ecdf, pattern_ecdf, competing_ecdf, shift_diff_ecdf, competing_diff_ecdf;
		//double shift_auc = 0.0, pattern_auc = 0.0, competing_auc = 0.0;

		TDay_Histogram shift_hist, pattern_hist, competing_hist;
		std::fill(shift_hist.begin(), shift_hist.end(), 0.0);
		std::fill(pattern_hist.begin(), pattern_hist.end(), 0.0);
		std::fill(competing_hist.begin(), competing_hist.end(), 0.0);

		size_t pattern_shift_better_cnt = 0, pattern_competing_better_cnt = 0,
			pattern_shift_worse_cnt = 0, pattern_competing_worse_cnt = 0;

		while (stats) {
			TError_Info record;
			if (stats.read(reinterpret_cast<char*>(&record), sizeof(record))) {

				if (record.ig != 0.0) {
					record.time_shift_rel = rel_err(record.time_shift, record.ig);
					record.pattern_rel = rel_err(record.pattern, record.ig);
					record.competing_rel = rel_err(record.competing, record.ig);


					data.push_back(record);
					shift_ecdf.push_back(record.time_shift_rel);
					pattern_ecdf.push_back(record.pattern_rel);
					competing_ecdf.push_back(record.competing_rel);

					//negative number means that we are better
					shift_diff_ecdf.push_back(record.pattern_rel - record.time_shift_rel);
					competing_diff_ecdf.push_back(record.pattern_rel - record.competing_rel);

					shift_hist[day_idx(record.device_time)] += record.time_shift_rel;
					pattern_hist[day_idx(record.device_time)] += record.pattern_rel;
					competing_hist[day_idx(record.device_time)] += record.competing_rel;

					if (record.pattern_rel < record.time_shift_rel) pattern_shift_better_cnt++;
					if (record.pattern_rel < record.competing_rel) pattern_competing_better_cnt++;

					if (record.pattern_rel > record.time_shift_rel) pattern_shift_worse_cnt++;
					if (record.pattern_rel > record.competing_rel) pattern_competing_worse_cnt++;
				}
			}
		}

		
				
		scgms::TSignal_Stats shift_stats, pattern_stats, competing_stats, shift_diff_stats, competing_diff_stats;
		Calculate_Signal_Stats(shift_ecdf, shift_stats);
		Calculate_Signal_Stats(pattern_ecdf, pattern_stats);
		Calculate_Signal_Stats(competing_ecdf, competing_stats);
		Calculate_Signal_Stats(shift_diff_ecdf, shift_diff_stats);
		Calculate_Signal_Stats(competing_diff_ecdf, competing_diff_stats);

		//at this point, we have all the data loaded and processed
		std::stringstream analyzed_csv;
		analyzed_csv << std::setprecision(std::numeric_limits<long double>::digits10 + 1);

		analyzed_csv << "; Shift; Pattern; BGLP" << std::endl;
		//analyzed_csv << "AUC; " << shift_auc << "; " << pattern_auc  << "; " << competing_auc << std::endl;
		analyzed_csv << "Avg; " << shift_stats.avg << "; " << pattern_stats.avg << "; " << competing_stats.avg << std::endl;
		analyzed_csv << "StdDev; " << shift_stats.stddev << "; " << pattern_stats.stddev << "; " << competing_stats.stddev << std::endl;
		analyzed_csv << "Sum; " << shift_stats.sum << "; " << pattern_stats.sum << "; " << competing_stats.sum << std::endl;
		analyzed_csv << "Count; " << shift_ecdf.size() << "; " << pattern_ecdf.size() << "; " << competing_ecdf.size() << std::endl;
		analyzed_csv << std::endl;
		analyzed_csv << "Min; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::min_value)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::min_value)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::min_value)] << std::endl;
		analyzed_csv << "Q1; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::p25)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::p25)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::p25)] << std::endl;
		analyzed_csv << "Med; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::median)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::median)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::median)] << std::endl;
		analyzed_csv << "Q2; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::p75)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::p75)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::p75)] << std::endl;
		analyzed_csv << "p95; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::p95)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::p95)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::p95)] << std::endl;
		analyzed_csv << "p99; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::p99)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::p99)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::p99)] << std::endl;
		analyzed_csv << "Max; " << shift_stats.ecdf[static_cast<size_t>(scgms::NECDF::max_value)] << "; " << pattern_stats.ecdf[static_cast<size_t>(scgms::NECDF::max_value)] << "; " << competing_stats.ecdf[static_cast<size_t>(scgms::NECDF::max_value)] << std::endl;
		
		
		analyzed_csv << std::endl << std::endl;

		analyzed_csv << "Pattern vs other; Shift; BGLP" << std::endl;
		analyzed_csv << "AUC less; " << pattern_stats.sum - shift_stats.sum << "; " << pattern_stats.sum - competing_stats.sum << std::endl;
		analyzed_csv << "AUC rel diff; " << shift_diff_stats.sum << "; " << competing_diff_stats.sum << std::endl;
		analyzed_csv << std::endl;

		analyzed_csv << "AVG less; " << static_cast<double>(pattern_stats.sum - shift_stats.sum)/static_cast<double>(pattern_ecdf.size()) << "; " << static_cast<double>(pattern_stats.sum - competing_stats.sum) / static_cast<double>(pattern_ecdf.size()) << std::endl;
		analyzed_csv << "AVG rel diff; " << static_cast<double>(shift_diff_stats.sum) / static_cast<double>(pattern_ecdf.size()) << "; " << static_cast<double>(competing_diff_stats.sum) / static_cast<double>(pattern_ecdf.size()) << std::endl;
		analyzed_csv << std::endl;

		analyzed_csv << "Times Better; " << pattern_shift_better_cnt << "; " << pattern_competing_better_cnt << std::endl;
		analyzed_csv << "Times Worse; " << pattern_shift_worse_cnt << "; " << pattern_competing_worse_cnt << std::endl;
		analyzed_csv << "Times Neutral; " << data.size() - pattern_shift_better_cnt - pattern_shift_worse_cnt << "; " << data.size() - pattern_competing_better_cnt - pattern_competing_worse_cnt << std::endl;

		//write analysis per quarter of hour
		analyzed_csv << std::endl << std::endl;
		analyzed_csv << "Time";		
		for (size_t i = 0; i < histogram_count; i++)
			analyzed_csv << "; " << i/4 << ":" << (i%4)*15;
		analyzed_csv << std::endl;

		analyzed_csv << "rattime";
		for (size_t i = 0; i < histogram_count; i++)			
			analyzed_csv << "; " << static_cast<double>(i) * 15.0 * scgms::One_Minute;
		analyzed_csv << std::endl;

		analyzed_csv << "Shift";
		for (size_t i = 0; i < histogram_count; i++)
			analyzed_csv << "; " << shift_hist[i];
		analyzed_csv << std::endl;

		analyzed_csv << "Pattern";
		for (size_t i = 0; i < histogram_count; i++)
			analyzed_csv << "; " << pattern_hist[i];
		analyzed_csv << std::endl;

		analyzed_csv << "Competing";
		for (size_t i = 0; i < histogram_count; i++)
			analyzed_csv << "; " << competing_hist[i];
		analyzed_csv << std::endl;

		//write analysis of ECDF
		analyzed_csv << std::endl << std::endl;
		
		analyzed_csv << "Probability";
		for (size_t i = static_cast<size_t>(scgms::NECDF::min_value); i <= static_cast<size_t>(scgms::NECDF::max_value); i++)
			analyzed_csv << "; " << i+1;
		analyzed_csv << std::endl;

		write_ecdf(analyzed_csv, "Pattern", pattern_stats);
		write_ecdf(analyzed_csv, "Shift", shift_stats);
		write_ecdf(analyzed_csv, "Competing", competing_stats);
		write_ecdf(analyzed_csv, "Shift diff", shift_diff_stats);
		write_ecdf(analyzed_csv, "Competing diff", competing_diff_stats);
	
		analyzed_csv << "Shift less";
		for (size_t i = static_cast<size_t>(scgms::NECDF::min_value); i <= static_cast<size_t>(scgms::NECDF::max_value); i++)
			analyzed_csv << "; " << pattern_stats.ecdf[i] - shift_stats.ecdf[i];
		analyzed_csv << std::endl;

		analyzed_csv << "Competing less";
		for (size_t i = static_cast<size_t>(scgms::NECDF::min_value); i <= static_cast<size_t>(scgms::NECDF::max_value); i++)
			analyzed_csv << "; " << pattern_stats.ecdf[i] - competing_stats.ecdf[i];
		analyzed_csv << std::endl;

		//write the relative errors
		analyzed_csv << std::endl << std::endl;
		analyzed_csv << "; 0; Re10; Re15; Re20; Re30; Max" << std::endl;
		std::vector<double> rel_errors = { 0.1, 0.15, 0.20, 0.3 };
		write_positions(analyzed_csv, "Pattern", pattern_ecdf, rel_errors);
		write_positions(analyzed_csv, "Shift", shift_ecdf, rel_errors);
		write_positions(analyzed_csv, "Competing", competing_ecdf, rel_errors);


		//write the diffs
		analyzed_csv << std::endl << std::endl;
		analyzed_csv << ";Min; -0.3; -0.2; -0.1; 0; 0.1; 0.2; 0.3; Max" << std::endl;
		std::vector<double> diffs = { -0.3, -0.2, -0.1, 0, 0.1, 0.2, 0.3 };
		write_positions(analyzed_csv, "Shift diff", shift_diff_ecdf, diffs);
		write_positions(analyzed_csv, "Competing diff", competing_diff_ecdf, diffs);

		//change decimal point to Czech locale..
		std::string locale_analysis = analyzed_csv.str();
//		std::replace(locale_analysis.begin(), locale_analysis.end(), '.', ',');

		//write the results
		const auto analyzed_csv_path = stats_filepath.replace_extension(".csv");
		std::fstream analyzed_csv_file{ analyzed_csv_path, std::ios::in | std::ios::out | std::ios::trunc };
		analyzed_csv_file << locale_analysis;
	}
}


extern "C" DLL_EXPORT void APIENTRY convert_d1namo() {
	auto d1namo_filepath = resolve_file_path_from_var(d1name_file_var_name);

	if (!d1namo_filepath.empty()) {
				
		std::string seg_num_str = d1namo_filepath.stem().string();
		if (seg_num_str.size() > 3) seg_num_str.resize(3);	//change to number
		


		std::fstream d1namo{ d1namo_filepath, std::ios::in};
		if (d1namo.is_open()) {
			std::string line, last_dt;
			std::vector<std::string> converted;
			converted.push_back("Logical Clock; Device Time; Event Code; Signal; Info; Segment Id; Event Code Id; Device Id; Signal Id");
			size_t clock = 1;

			std::getline(d1namo, line);	//ignore the header file

			bool seg_start_emitted = false;			

			auto emit_start_stop = [&](const bool start) {
				std::string marker_line = std::to_string(clock++);
				marker_line += "; " + last_dt;
				marker_line += ";;;;" + seg_num_str;
				marker_line += start ? ";9;" : ";10;";
				marker_line += "{7DA3E37B-FE88-4E95-993D-A1BB78638414}; {00000000-0000-0000-0000-000000000000}";
				converted.push_back(marker_line);
			};

			//read and process the file			
			while (std::getline(d1namo, line)) {
				//resolve signal
				const auto is_cgms = line.find("cgm") != std::string::npos;

				const auto st_delim = line.find(",");
				if (st_delim != std::string::npos) {
					line[st_delim] = ' ';
					const auto nd_delim = line.find(",");

					const auto rd_delim = line.find(",", nd_delim + 1);


					

					if ((nd_delim != std::string::npos) && (rd_delim != std::string::npos)) {
						
						last_dt = line.substr(0, nd_delim);

						if (!seg_start_emitted) {
							emit_start_stop(true);

							seg_start_emitted = true;
						}


						std::string level = line.substr(nd_delim+1, rd_delim - nd_delim-1);

						std::string log_line = std::to_string(clock++);
						log_line += "; "+ last_dt;
						log_line += ";;;" + level + ";" + seg_num_str;
						log_line += ";2; {7DA3E37B-FE88-4E95-993D-A1BB78638414};";
						log_line += is_cgms ? "{3034568D-F498-455B-AC6A-BCF301F69C9E}" : "{F666F6C2-D7C0-43E8-8EE1-C8CAA8F860E5}";

						converted.push_back(log_line);
					}
				}

			}
			emit_start_stop(false);
		
			//and write the new log content (which we must polish with log-replay-log anyway to keep this code small
			d1namo_filepath = d1namo_filepath.replace_extension(".imperfect.log");
			std::ofstream converted_file{ d1namo_filepath};
			for (const auto& line : converted) {
				converted_file << line << std::endl;
			}
		}
	}
}
