/*****************************************************************************
 *
 * Copyright (c) 2018-19, Josef Treus
 * Westfälische Wilhelms-Universität Münster, AG Embedded Systems
 * All rights reserved.
 *
 * This file is part of SysCSec
 *
 * SysCSec is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * SysCSec 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 General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SysCSec.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *  Please report any problems or bugs to: sysc.sec@uni-muenster.de
 *
 ****************************************************************************/




#ifndef RessourceHandler_H
#define RessourceHandler_H


#include <iostream>
#include <sstream>
#include "Utils.cpp"
#include "parameters.h"

using namespace std;


 /* The RessourceHandler tracks Power consumption
  * additionally, it provides methods to evaluate the power consumption
  */
class RessourceHandler {
private:
	
	
	int timeStart;
	map<int, string> specialValues;

	/* returns a defined part of the trace
	 * Param:	includeGaps : defines whether values below the threshold should be copied as well
	 * 		 	threshold : only higher values should be copied
	 * 			begin : start monitoring
	 * 			end : end monitoring
	 * 			trace : power trace
	 * Return : defined part of the trace
  	 */
	map<int,int> getTraceSection(bool includeGaps, int threshold, int begin, int end, map<int,int>trace){
		map <int,int> section;
		for(int i = begin; i <= end; i++){
			if(includeGaps){
				section[i-begin] = trace[i];
			}else{
				section[i-begin] = (trace[i] > threshold)? trace[i] : 0;
			} 
		}
		return section;
	}

	RessourceHandler() {
	}

	RessourceHandler(const RessourceHandler&);
	RessourceHandler& operator=(const RessourceHandler&);

public:
	int traceCounter;
	
	map<int,map<int,int>> powerTraces;
	map<int,map<int,int>> additionalTraces;
	map<int,map<int,int>> corrStrength;

	/* returns a static instance of the RessourceHandler
	 * Param : -
	 * Return : Pointer to the RessourceHandler object
  	 */
	static RessourceHandler* getInstance() {
		static RessourceHandler instance;
		return &instance;
	}


	/* partitions a trace according to its peaks
	 * Param:	includeGaps : defines whether values below the threshold should be copied as well
	 * 		 	threshold : only higher values are part of a peak
	 * 			minSizeOfPeak : minimal area below a peak
	 * 			maxGapSize : maximal distance between values within a peak
	 * 			trace : power trace
	 * Return : a number of smaller power traces that emphasize the peaks of the original trace
  	 */
	map<int, map<int,int>> getPeaksOfTrace(bool includeGaps, int threshold, int minSizeOfPeak, int maxGapSize, map<int,int>trace){
		map<int,map<int,int>> peakSections;
        int underThresholdCounter = 0;
		int surface = 0;
        int colIndex = 0;
		int begin = 0;
		int end = 0;
		int beginPreviousSec = 0;
        for(int i = 0; i <= trace.rbegin()->first; i++){
			
                if(trace[i] > threshold){
					if(surface == 0 && end != 0){
							beginPreviousSec = begin;
							begin = end + (i-end)/2 +1;
							peakSections[colIndex-1] = getTraceSection(includeGaps, threshold, beginPreviousSec, (end + (i-end)/2), trace);
					}
                   	surface += trace[i];
                    underThresholdCounter = 0;
                }else if(surface != 0 && underThresholdCounter < maxGapSize){
                    underThresholdCounter++;
				}else if(surface > minSizeOfPeak){
					end = i - underThresholdCounter -1;
					surface = 0;
					underThresholdCounter = 0;
					colIndex++;
				}
				if(surface != 0 && i == trace.rbegin()->first){
					if(surface > minSizeOfPeak){
						peakSections[colIndex] = getTraceSection(includeGaps, threshold, begin, i, trace);
					}else{
						peakSections[colIndex-1] = getTraceSection(includeGaps, threshold, beginPreviousSec, i, trace);
					}
				}
        }
		return peakSections;
	}

	/*  calculate the average of a set of traces
	 * Param:	tracePile : set of traces
	 * Return : average trace
  	 */
	map<int,int> meanTrace(map<int,map<int,int>> tracePile){
        map<int,int> mean;
        int traceAmount = tracePile.size();
		assert(traceAmount > 0);
        for(int i=0; i < traceAmount; i++) {
            for(int j=0; j < tracePile[i].rbegin()->first; j++) {
               mean[j] += tracePile[i][j];
            }
        }
		for(int j=0; j <= mean.rbegin()->first; j++) {
               mean[j] /= traceAmount;
        }
        return mean;
    }

	/*  Calculate square deviation of each value in a trace 
	 * Param:	trace 
	 * Return : variance
  	 */
	long double traceVariance(map<int,int> trace){
       	long long variance = 0;
        for(int i=0; i < trace.rbegin()->first; i++) {
               variance += trace[i] * trace[i];
        }
        return variance/(double)(trace.rbegin()->first+1);
    }

	/*  Calculate the sum of all values in a trace
	 * Param:	trace 
	 * Return : sum
  	 */
	long long traceSum(map<int,int> trace){
       	long long sum = 0;
        for(int i=0; i <= trace.rbegin()->first; i++) {
               sum += trace[i];
        }
        return sum;
    }

	/*  Calculate difference between two traces
	 * Param:	traceZero : first trace
	 * 			traceOne : second trace
	 * Return : difference trace
  	 */
	map<int,int> traceDifference(map<int,int> traceZero, map<int,int> traceOne){
		map <int,int> difference;
		int length = (traceZero.rbegin()->first > traceOne.rbegin()->first)? traceZero.rbegin()->first : traceOne.rbegin()->first;
		for(int i=0; i <= length; i++) {
            difference[i]= traceZero[i] - traceOne[i];
			difference[i] = (difference[i] < 0)? (-1)*difference[i] : difference[i];
        }
		return difference;
	}

	/*  calls Utils Method to write power traces, average power trace, 
	 *  optional traces and correlation matrix to file.
	 * Param:	-
	 * Return : -
  	 */
	void logToFile() {
		map<int,int> meanOfAllTraces;
		if(powerTraces.size() > 0){
			meanOfAllTraces = meanTrace(powerTraces);
		}
		Utils::writeToJSObject(powerTraces, meanOfAllTraces , additionalTraces, corrStrength);
	}

	/* finishes trace writing
	 * Param:	-
	 * Return : -
  	 */
	void endTrace(){
		if(powerTraces[traceCounter].size() > 0){
			if(NOISEDEGREE > 0){
				for(int i = 0; i <= powerTraces[traceCounter].rbegin()->first; i++){
					powerTraces[traceCounter][i] = Utils::normalDistributedValue(powerTraces[traceCounter][i], NOISEDEGREE);
				}
			}
			traceCounter++;
		}
	}

	/* log power consumption over time
	 * Param:	start: start time
	 * 			duration: interval length
	 * 			amount: strength of power consumption
	 * Return : -
  	 */
	void logRessourceUsage(int start, int duration, int amount) {
		if(powerTraces[traceCounter].size() == 0){
			map<int,int> tempMap;
			powerTraces[traceCounter] = tempMap;
		}
		int sizeOfActTrace = powerTraces[powerTraces.size()-1].size();
		if(sizeOfActTrace == 0){
		 timeStart = start;
		}
		/** write to map **/
		for(int i = start; i < start+duration; i++){
			powerTraces[powerTraces.size()-1][i-timeStart] += amount;
		}
	}
	/* count number of switching bits in a binary add.
	 * Param:	a : first number
	 * 			b : second number
	 * Return : number of switched bits
  	 */
	int addModel (int a, int b, bool carry){
		if(a == 0 && b == 0 && carry == false || a < 0 || b < 0){
			return 0;
		}
		bool bitOne = (a % 2 > 0)? true : false;
		bool bitTwo = (b % 2 > 0)? true : false;
		if(!carry){
			if(bitOne == 0 && bitTwo == 0){
				//one bit input: 0 changed bits: 0
				return addModel(a/2, b/2, false);
			}else if (bitOne == 0 && bitTwo == 1){
				//one bit input: 1 changed bits: 1
				return 2 + addModel(a/2, b/2, false);
			}else if(bitOne == 1 && bitTwo == 0){
				//one bit input: 1 changed bits: 0
				return 1 + addModel(a/2, b/2, false);
			}else{
				//one bit input: 2 changed bits: 1
				return 3 + addModel(a/2, b/2, true);
			}
		}else{
			if(a == 0 && b == 0){
				//one bit input: 0 changed bits: 1
				return 1 + addModel(a/2, b/2, false);
			}else if (bitOne == 0 && bitTwo == 1){
				//one bit input: 1 changed bits: 0
				return 1 + addModel(a/2, b/2, true);
			}else if (bitOne == 1 && bitTwo == 0){
				//one bit input: 1 changed bits: 0
				return 1 + addModel(a/2, b/2, true);
			}else{
				//one bit input: 2 changed bits: 0
				return 2 + addModel(a/2, b/2, true);
			}
		}
	}
	
	
	/* count number of switching bits in a binary subtraction.
	 * Param:	a : first number
	 * 			b : second number
	 * Return : number of switched bits
  	 */
	int subModel (int a, int b, bool carry){
		if(a == 0 && b == 0 && carry == false || a < b){
			return 0;
		}
		bool bitOne = (a % 2 > 0)? true : false;
		bool bitTwo = (b % 2 > 0)? true : false;
		if(!carry){
			if(bitOne == 0 && bitTwo == 0){
				//one bit input: 0 changed bits: 0
				return subModel(a/2, b/2, false);
			}else if (bitOne == 0 && bitTwo == 1){
				//one bit input: 1 changed bits: 2
				return 3 + subModel(a/2, b/2, true);
			}else if(bitOne == 1 && bitTwo == 0){
				//one bit input: 1 changed bits: 0
				return 1 + subModel(a/2, b/2, false);
			}else{
				//one bit input: 2 changed bits: 0
				return 2 + subModel(a/2, b/2, false);
			}
		}else{
			if (bitOne == 0 && bitTwo == 0){
				//one bit input: 0 changed bits: 1
				return 1 + subModel(a/2, b/2, true);
			}else if(bitOne == 1 && bitTwo == 0){
				//one bit input: 1 changed bits: 0
				return 1 + subModel(a/2, b/2, false);
			}else if(bitOne == 0 && bitTwo == 1){
				//one bit input: 1 changed bits: 0
				return 1 + subModel(a/2, b/2, true);
			}else{
				//one bit input: 2 changed bits: 0
				return 2 + subModel(a/2, b/2, true);
			}
		}
	}
	
	/* count number of switching bits in a binary multiplication.
	 * Param:	a : first number
	 * 			b : second number
	 * Return : number of switched bits
  	 */
	int mulModel (int a, int b){
		int returnvalue = 0;
		if(a == 0 && b == 0 || a < 0 || b < 0){
			return returnvalue;
		}
		int bTemp = b;
		int aTemp = a;
		int erg = 0;
		// evaluate bit by bit
		for(int i = 0; bTemp > 0; i++){
			// if the current bit is 1, a is multiplied with the corresponding exponent
			if(bTemp % 2 == 1){
				returnvalue += addModel(erg, a*std::pow(2,i), false);
				erg = erg + a*std::pow(2,i);
			}
			bTemp = bTemp / 2;
		}
		return returnvalue;
	}

	/* count number of switching bits in a binary division - NOT IMPLEMENTED YET.
	 * Param:	a : first number
	 * 			b : second number
	 * Return : number of switched bits
  	 */
	int divModel(int a, int b){
		return 10;
	}
  
	/* count number of switching bits in a binary modulo operation
	 * Param:	a : first number
	 * 			b : second number
	 * Return : number of switched bits
  	 */	
	int modModel(int a, int b){
		if(a < b){
			return 1;
		}
		// number of necessary subtractions
		int timesBigger = Utils::unsignificantBitCount(b, NBITS) - Utils::unsignificantBitCount(a, NBITS);
		int res = 0;
		// effort of necessary subtractions
		int bShifted = 0;
		for(int i = 0; i <= timesBigger; i++){
			bShifted = b << (timesBigger - i);
			if(a > bShifted){
				res += subModel(a, bShifted, false);
				a -= bShifted;
			}
		}
		//power consumption of the output
		res += Utils::hammingWeight(a);
		return res;
	}
  
	/* Calculate power consumption of a shift operation
	 * Param:  	b : number of shifts
	 * Return :  number of shifts if positive
  	 */
	int shiftModel(int b){
		if(b < 0){
			return 0;
		}
		return b;
	}
	/* Calculate power consumption of a comparison
	 * Param:  	-
	 * Return : number of compared bits
  	 */
	int compareModel(){
		return NBITS;
	}

	/* Calculate power consumption of a method call
	 * Param:  	-
	 * Return : 3 - estimated value
  	 */
	int callModel(){
		return 3;
	}

	/* Calculate power consumption of a method assignment
	 * Param:  	a = old value
	 * 			b = new value
	 * Return : estimated values
  	 */
	int assignModel(int a, int b){
		if(a == b){
			return 1;
		}else{
			return 2;
		}
	}
	
	
	
	/* track power consumption of a given operation
	 * Param:  	recordConsumption = should the power consumption be considered?
	 * 			operation = kind of operation
	 * 			a = first operand
	 * 			b = second operand
	 * Return : -
  	 */
	void logOperationPowerExpense(bool recordConsumption, int operation, int a, int b){
		if(a < 0 || b < 0)
			return;
		if(recordConsumption){
			stringstream ss;
			ss << sc_time_stamp().value();
			long time = stol(ss.str())/TIME_INTERVAL;
			
			switch(operation){
				case ADD:
					logRessourceUsage(time, addModel(a,b,false)/5+1, addModel(a,b,false)+5);
					break;
				case SUB:
					logRessourceUsage(time, subModel(a, b, false)/5+1, subModel(a, b, false)+5);
					break;
				case MUL:
					logRessourceUsage(time, mulModel(a,b)/5+2, mulModel(a,b)+20);
					break;
				case DIV:
					logRessourceUsage(time, divModel(a,b)/5+1, divModel(a,b)+20);
					break;
				case MOD:
					logRessourceUsage(time,  modModel(a,b)/5+1, modModel(a,b)+15);
					break;
				case SHIFT:
					logRessourceUsage(time, 1, shiftModel(b));
					break;
				case COMPARE:
					logRessourceUsage(time, 1, compareModel());
					break;
				case CALL:
					logRessourceUsage(time, 1, callModel());
					break;
				case ASSIGN:
					logRessourceUsage(time, 1, assignModel(a, b));
					break;

			}
		
			
			
		

			wait(5, SC_NS);
		}
	}
};

#endif
