/*****************************************************************************
 *
 * 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: joseftreus@web.de
 *
 ****************************************************************************/

#ifndef UTILS_CPP
#define UTILS_CPP


#include <stdint.h>
#include <string> 
#include <iostream>
#include <sstream> 
#include <fstream>
#include <cassert>
#include <random>
#include <chrono>

#include "parameters.h"
// some helper function used by RSA and ResourceHandler
using namespace std;
class Utils{


	private:
	public:
	/* Print a map on the console
	 * Param :  toPrint = map to print
	 * Return : -
  	 */
	static void printMap(map<int,int> toPrint){
		for(auto it = toPrint.begin(); it != toPrint.end(); it++){
			cout << "first: " << it->first << " second: " << it->second << endl;
		}
	}

	/* Writes a data.js file from the given values
	 * Param:   powerTraces = trace set
	 * 			meanTrace = mean trace
	 * 			additionalTrace = additional traces (may be empty)
	 * 			corrStrength = C-Matrix from the CCA attack
	 * Return : -
  	 */
	static void writeToJSObject(map<int,map<int,int>> powerTraces, map<int,int> meanTrace, map<int,map<int,int>> additionalTraces ,map<int,map<int,int>> corrStrength) {
		ofstream myfile;

		myfile.open("data.js");

		cout << "writing " << powerTraces.size()  << " powerTraces" <<endl;
		myfile << writeMapToString(powerTraces, "powerTraces");

		
		if(powerTraces.size() > 0){
		  cout << "power trace starts" << endl;
			myfile << "var meanTrace = [" << endl;
			for(int i = 0; i <= meanTrace.rbegin()->first; i++){
				myfile << meanTrace[i] << ", ";
			}
			myfile << "];"<< endl; 			
		}
		myfile << writeMapToString(additionalTraces, "additionalTraces");
		myfile << writeMapToString(corrStrength, "corrStrength");

		myfile.close();
	}

	/* Write traces in JS Format and return as string
	 * Param:  	data = set of traces
	 * 			varname = name of the array in JavaScript format
	 * Return : data as string in JS Format
  	 */
	static string writeMapToString(map<int,map<int, int>> data, string varName){
		stringstream streamedMap;
		if(data.size() > 0){
			streamedMap << "var " << varName << " = [" << endl;
			for(auto itOut = data.begin(); itOut != data.end(); itOut++) {
					streamedMap << "[";
					int lastVal = 0;
					for(auto itIN = itOut->second.begin(); itIN != itOut->second.end(); itIN++){
							
						for(int i = lastVal+1; i < itIN->first; i++){
							streamedMap << "0, ";
						}		
						streamedMap <<  itIN->second  << ", ";
						lastVal = itIN->first;
					} 
					streamedMap << "]," << endl;
			}
			streamedMap << "];" << endl;
		}
		return streamedMap.str();
	}


	/* Returns a normal dsitribution value using muh and sigma 
	 * Param:  	muh expectancy value
	 * 			sig standard deviation
	 * Return : value according to normal distribution
  	 */
	static double normalDistributedValue(double muh, double sig){
		std:: default_random_engine randomGenerator;
		randomGenerator.seed(std::chrono::system_clock::now().time_since_epoch().count());
		std::normal_distribution<double> distrN(muh,sig);
		return distrN(randomGenerator);
	}


	/* Returns the Hamming weight (counts one bits)
	 * Param:  	number 
	 * Return : Hamming weight
	 */
	static int hammingWeight(int number) {
		if(number < 1){
			return 0;
		}
		return (number%2) + hammingWeight(number >> 1);
	}
	
	/* returns Hamming distance (number of differing bits)
	 * Param:  	x first number
	 * 		y second number
	 * Return : Hamming distance
  	 */
	static int hamming_distance(int x, int y)
	{
		int dist = 0;
		
		// Count the number of bits set
		for (int val = x ^ y; val > 0; val /= 2)
		{
			// If A bit is set, so increment the count
			if (val & 1)
				dist++;
			// Clear (delete) val's lowest-order bit
		}

		// Return the number of differing bits
		return dist;
	}

	/* Returns the median of an array
	 * Param:  	min = known smallest value
	 * 			max = known greatest value
	 * 			array = array
	 * 			arrayLength = length of the array
	 * Return : median
  	 */
	static int median(int min, int max, int* array, int arrayLength){
        int middle = 0;
        int countLow = 0;
        int countHigh = 0;
		// logarithmic search
        while(true){
			middle = (max + min) / 2;
            countLow = 0;
            countHigh = 0;
            for(int z = 0; z < arrayLength; z++){
                if(array[z] < middle){
                    countLow++;
                }else if(array[z] > middle){
                    countHigh++;
                }
            }

            if(!(arrayLength - countLow >= arrayLength/2)){
                max = middle;
            }else if(!(arrayLength - countHigh >= arrayLength/2)){
                min = middle;
            }else{
                break;
            }
        }
        return middle;
    }

	/* Computes the average of an array.
	 * Param:  	val = array
	 * 			length = length of the array
	 * Return : average of the array values
  	 */
	static double mean(int*val, int length){
        long long res = 0;
        for(int i = 0; i < length; i++){
            if(res + val[i] > numeric_limits<long long>::max()){
                cout << "Fehler beim Durschnitt berechnen";
            }else{
                res += val[i];
            }
        }
        return (double)res/length;
    }
	/* Counts the number of zero bits at the beginning of a number
	 * Param:  	number 
	 * 			actSize 
	 * Return : number of leading zeros
  	 */
	static int unsignificantBitCount(int number, int actSize){
		int unsignificantBitcount = 0;
		int temp = 0;
		for(int i = actSize-1; i >= 0; i--){
				temp = number;
				temp >>= i;
				if(temp%2 == 0){
                	unsignificantBitcount++;
				}else{
					return unsignificantBitcount;
				}
        }
		return actSize;
	}

	/* Computes the standard deviation for a set of values.
	 * Param:  	values = array with values
	 * 			length = number of values
	 * Return : standard deviation
  	 */
	static double standardDeviation(int* values, int length){
		double erg = 0;
		if(length > 0){
			for(int i = 0; i < length; i++){
				erg += pow(values[i] - mean(values,length), 2);
			}
			erg /= (double)length;
		}
		return sqrt(erg);
	}

	/* Returns a non-negative normally distributed number.
	 * Param:  	muh = expectancy value of the normal distribution
	 * 			sig = standard deviation of the normal distribution
	 * Return : non-negative normally distributed number
  	 */
	double noNegativeNormalDist(double muh, double sig){
		double erg = -1;
		while(erg < 0){
			erg = normalDistributedValue(muh, sig);
		}
		return erg;
	}
};


#endif
