//===-- rosa/agent/Reliability.h --------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/Reliability.h
///
/// \author Daniel Schnoell (danielschnoell@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Declaration of `rosa::Reliability` base-class.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_RELIABILITY_H
#define ROSA_AGENT_RELIABILITY_H

#include "rosa/agent/FunctionAbstractions.hpp"
#include "rosa/agent/Functionality.h"
#include "rosa/agent/RangeConfidence.hpp"

#include <vector>

namespace rosa {
	namespace agent {

		template <typename ReliabilityType, typename StateType,
			typename SensorValueType>
			class LowLevel {
			struct ConfOrRel {
				StateType score;
				ReliabilityType Reliability;
			};

			std::vector<std::vector<ConfOrRel>> History;
			std::size_t HistoryMaxSize;
			std::vector<ConfOrRel> ValuesFromMaster;

			SensorValueType previousSensorValue;
			unsigned int valueSetCounter;
			std::vector<StateType> States;
			bool PreviousSensorValueExists = false;

			RangeConfidence<ReliabilityType, StateType, SensorValueType>* Confidence;
			Abstraction<SensorValueType, ReliabilityType>* Reliability;
			Abstraction<SensorValueType, ReliabilityType>* ReliabilitySlope;
			Abstraction<std::size_t, ReliabilityType>* TimeConfidence;

			ReliabilityType getRelibility(SensorValueType actualValue, SensorValueType lastValue, unsigned int valueSetCounter) {

				ReliabilityType relAbs = Reliability->operator()(actualValue);
				ReliabilityType relSlo = ReliabilitySlope->operator()((lastValue - actualValue) / (SensorValueType)valueSetCounter);

				// calculate signal input reliability
				// NOTE: options would be multiply, average, AND (best to worst:
				// average = AND > multiply) rel = relAbs * relSlo; rel = (relAbs +
				// relSlo)/2;

				return std::min(relAbs, relSlo);
			}

			std::vector<ConfOrRel> getAllPossibleScoresBasedOnHistory(std::vector<ConfOrRel> possibleScores) {
				//iterate through all history entries
				std::size_t posInHistory = 0;
				typedef typename std::vector<std::vector<ConfOrRel>>::iterator iter;
				for (iter pShE = History.begin(); pShE < History.end(); pShE++, posInHistory++) {


					//iterate through all possible scores of each history entry
					for (typename std::vector<ConfOrRel>::iterator pSh : *pShE) {

						//printf("a3\n");

						int historyScore = pSh->score;
						float historyConf = pSh->Reliability;

						//combine each history score with the confidence of time
						//NOTE: multiplication, AND, or average would be alternatives (best to worst: multiplication = AND = average)
						historyConf = historyConf * TimeConfidence(posInHistory);
						//historyConf = (historyConf + TimeConfidence(posInHistory)) / 2;
						//historyConf = std::min(historyConf, TimeConfidence(posInHistory));

						//printf("a4\n");

						bool foundScore = false;
						for (ConfOrRel& pS : possibleScores) {

							if (pS->score == historyScore) {

								//calculate confidence for score
								//NOTE: multiplication, AND, or average would be alternatives (best to worst: AND >> average = multiplication )
								//pS->confOrRel = pS->confOrRel * historyConf;
								//pS->confOrRel = (pS->confOrRel + historyConf) / 2;
								pS->confOrRel = std::max(pS->confOrRel, historyConf);

								foundScore = true;
							}
						}

						if (foundScore == false) {

							ConfOrRel possibleScore;
							possibleScore.score = historyScore;
							possibleScore.Reliability = historyConf;

							possibleScores->push_back(possibleScore);

						}
					}
				}

				return possibleScores;
			}

			void saveInHistory(std::vector<ConfOrRel> actualPossibleScores) {

				//check if the reliability of at least one possible score is high enough
				bool atLeastOneRelIsHigh = false;
				for (ConfOrRel pS : actualPossibleScores) {
					if (pS.Reliability > 0.5) {
						atLeastOneRelIsHigh = true;
					}
				}
				//save possible scores if at least one possible score is high enough (or if the history is empty)
				if (History.size() < 1 || atLeastOneRelIsHigh == true) {

					History.push_front(actualPossibleScores);

					//if history size is higher than allowed, savo oldest element
					while (History.size() > HistoryMaxSize) {
						//delete possibleScoreHistory.back();
						History.pop_back();
					}
				}
			}


			ConfOrRel operator()(SensorValueType SensorValue) {
				std::map<StateType, ReliabilityType> ActuallPosibleScores_tmp = Confidence(SensorValue);

				std::vector<ConfOrRel> ActuallPossibleScores;
				for (auto state : States)
					ActuallPossibleScores.push_back({ state, ActuallPosibleScores_tmp.find(state) });

				ReliabilityType inputReliability;

				if (PreviousSensorValueExists)
					inputReliability = getRelibility(SensorValue, previousSensorValue, valueSetCounter);
				else
					inputReliability = Reliability(SensorValue);

				for (std::size_t at = 0; at < ActuallPossibleScores.size(); at++)
					ActuallPossibleScores.at(at) = std::min(ActuallPossibleScores.at(at), inputReliability);

				for (std::size_t APS_at = 0; APS_at < ActuallPossibleScores.size(); APS_at++)
					for (std::size_t VFM_at = 0; VFM_at < ValuesFromMaster.size(); VFM_at++) {
						if (ActuallPossibleScores.at(APS_at).score == ValuesFromMaster.at(VFM_at).score) {
							ActuallPossibleScores.at(APS_at).Reliability = ActuallPossibleScores.at(APS_at).Reliability + ValuesFromMaster.at(VFM_at).Reliability;
						}
					}

				saveInHistory(ActuallPossibleScores);

				std::vector<ConfOrRel> possibleScores;

				getAllPossibleScoresBasedOnHistory(&possibleScores);


				std::sort(possibleScores.begin(), possibleScores.end(), [](ReliabilityType A, ReliabilityType B)-> bool {return A > B; });


				previousSensorValue = SensorValue;
				PreviousSensorValueExists = true;
				return possibleScores.at(0);

			}
		};


		template<typename ReliabilityType, typename StateType>
		class HighLevel
		{
			struct ConfOrRel {
				StateType score;
				ReliabilityType Reliability;
			};

			CrossReliability


			std::vector<ConfOrRel> operator(std::vector<ConfOrRel> Values)
			{

				int EWS = 0;
				float combinedInputRel = 1;
				float combinedCrossRel = 1;
				float outputReliability;

				unsigned int numOfSlaveAgentsWereChecked = 0;

				for (auto Value:Values) {
					StateType sc=Value.score;
					ReliabilityType rel=Value.Reliability;

					EWS = EWS + sc;
					
					combinedInputRel = std::min(combinedInputRel, rel);


					//calculate the cross reliability for this slave agent
					float realCrossReliabilityOfSlaveAgent = crossReliabilityNEW(sA, mountedSlaveAgents, CONJUNCTION, 0); //AVERAGE, MULTIPLICATION, CONJUNCTION (best to worst: AVERAGE = CONJUNCTION > MULTIPLICATION >> )

					//send all theoritcally possible scores to slave agent

					sA->pass_msgToSendBuffer(ISA_SuggestedAbstractedSensoryDataAndReliability);
					for (int theoreticalScore = 0; theoreticalScore <= 3; theoreticalScore++) {
						//calculate the cross reliability for this slave agent
						float theoreticalCrossReliabilityOfSlaveAgent = crossReliabilityNEW(sA, mountedSlaveAgents, CONJUNCTION_THEORETICAL, theoreticalScore); //AVERAGE_THEORETICAL, MULTIPLICATION_THEORETICAL, CONJUNCTION_THEORETICAL (best to worst: AVERAGE_THEORETICAL = CONJUNCTION_THEORETICAL > MULTIPLICATION_THEORETICAL >> )
						sA->pass_msgToSendBuffer(theoreticalScore);
						sA->pass_msgToSendBuffer(theoreticalCrossReliabilityOfSlaveAgent);
					}
					sA->send_msgs();


					combinedCrossRel = std::min(combinedCrossRel, realCrossReliabilityOfSlaveAgent);

					numOfSlaveAgentsWereChecked++;
				}

				//combine cross reliabilites and input reliabilites of all slave agents
				//NOTE: options would be multiply, average, AND (best to worst: )
				//outputReliability = combinedInputRel * combinedCrossRel;
				//outputReliability = (combinedInputRel + combinedCrossRel) / 2;
				outputReliability = std::min(combinedInputRel, combinedCrossRel);
			}

		};
	} // namespace agent
}// namespace rosa
#endif // !ROSA_AGENT_RELIABILITY_H
