#include <iostream>
#include <iomanip>
#include <chrono>
#include <vector>

using namespace std;
using namespace std::chrono;

void testCacheLocality(){
	cout<<"\n--- 1. ΤΕΣΤ ΤΟΠΙΚΟΤΗΤΑΣ ΜΝΗΜΗΣ ---"<<endl;
	const int SIZE=10000;
	int** matrix=new int*[SIZE];
	for(int i=0;i<SIZE;i++){
		matrix[i]=new int[SIZE];
	}
	for(int i=0;i<SIZE;i++){
		for(int j=0;j<SIZE;j++){
			matrix[i][j]=0;
		}
	}
	cout<<"\nΤεστ 1: Προσπέλαση κατά γραμμές..."<<endl;
	auto startRow=high_resolution_clock::now();
	for(int i=0;i<SIZE;i++){
		for(int j=0;j<SIZE;j++){
			matrix[i][j]++;
		}
	}
	auto stopRow=high_resolution_clock::now();
	auto durationRow=duration_cast<milliseconds>(stopRow-startRow);
	cout<<"Time: "<<durationRow.count()<<" ms"<<endl;
	cout<<"Τεστ 2: Προσπέλαση κατά στήλες..."<<endl;
	auto startCol=high_resolution_clock::now();
	for(int j=0;j<SIZE;j++){
		for(int i=0;i<SIZE;i++){
			matrix[i][j]++;
		}
	}
	auto stopCol=high_resolution_clock::now();
	auto durationCol=duration_cast<milliseconds>(stopCol-startCol);
	cout<<"Time: "<<durationCol.count()<<" ms"<<endl;
	cout<<"\nΣΥΜΠΕΡΑΣΜΑ: Η σειριακή προσπέλαση μνήμης είναι ΠΟΛΥ πιο γρήγορη λόγω της CPU Cache!"<<endl;
	for(int i=0;i<SIZE;i++){
		delete[] matrix[i];
	}
	delete[] matrix;
}

void testFlatVsPointerArray(){
	cout<<"\n--- 2. **(ΚΑΤΑΚΕΡΜΑΤΙΣΜΕΝΟ) VS 1D ΕΠΙΠΕΔΟ HEAP ---"<<endl;
	const int SIZE=8000;
	auto startPtr=high_resolution_clock::now();
	int** ptrMatrix=new int*[SIZE];
	for(int i=0;i<SIZE;i++){
		ptrMatrix[i]=new int[SIZE];
		for(int j=0;j<SIZE;j++){
			ptrMatrix[i][j]=i+j;
		}
	}
	auto stopPtr=high_resolution_clock::now();
	auto durPtr=duration_cast<milliseconds>(stopPtr-startPtr);
	auto startFlat=high_resolution_clock::now();
	int* flatMatrix=new int[SIZE*SIZE];
	for(int i=0;i<SIZE;i++){
		for(int j=0;j<SIZE;j++){
			flatMatrix[i*SIZE+j]=i+j;
		}
	}
	auto stopFlat=high_resolution_clock::now();
	auto durFlat=duration_cast<milliseconds>(stopFlat-startFlat);
	cout<<"Time for ** array: "<<durPtr.count()<<" ms"<<endl;
	cout<<"Time for 1D array: "<<durFlat.count()<<" ms"<<endl;
	cout<<"Ο 1D είναι ταχύτερος και ασφαλέστερος για την μνήμη!"<<endl;
	for(int i=0;i<SIZE;i++){
		delete[] ptrMatrix[i];
	}
	delete[] ptrMatrix;
	delete[] flatMatrix;
}

struct HeavyData{
	int data[100000];
};

//Κληση με τιμη αρα αντιγραφονται ολα στο stack
void processByValue(HeavyData hd){
	hd.data[0]=1;
}

//Κληση με διευθυνση αρα μονο 8 bytes αντιγραφονται
void processByPointer(HeavyData* hd){
	hd->data[0]=1;
}

void testFunctionCallSpeed(){
	cout<<"\n--- 3. ΤΑΧΥΤΗΤΑ ΚΛΗΣΗΣ ΣΥΝΑΡΤΗΣΕΩΝ ---"<<endl;
	HeavyData* hd=new HeavyData();
	const int ITERATIONS=10000;
	auto startVal=high_resolution_clock::now();
	for(int i=0;i<ITERATIONS;i++){
		processByValue(*hd);
	}
	auto stopVal=high_resolution_clock::now();
	auto durVal=duration_cast<milliseconds>(stopVal-startVal);
	auto startPtr=high_resolution_clock::now();
	for(int i=0;i<ITERATIONS;i++){
		processByPointer(hd);
	}
	auto stopPtr=high_resolution_clock::now();
	auto durPtr=duration_cast<milliseconds>(stopPtr-startPtr);
	cout<<"Time of call by value: "<<durVal.count()<<" ms"<<endl;
	cout<<"Time of call by pointer: "<<durPtr.count()<<" ms"<<endl;
	cout<<"Άρα ποτέ δεν περνάμε μεγάλα αβτικείμενα ή πίνακες με τιμή. Χρησιμοποιούμε ΠΑΝΤΑ δείκτες!"<<endl;
	delete hd;
}

void showMenuPart4(){
	cout<<"\n====================================="<<endl;
	cout<<"  ΕΚΠΑΙΔΕΥΤΙΚΟ ΜΕΝΟΥ - ΑΠΟΔΟΣΗ"<<endl;
	cout<<"====================================="<<endl;
	cout<<"1. Τεστ Cache Locality"<<endl;
	cout<<"2. Ταχύτητα Heap: 2D (**) vs Flat 1D"<<endl;
	cout<<"3. Ταχύτητα Κλήσεων"<<endl;
	cout<<"0. Έξοδος."<<endl;
	cout<<"====================================="<<endl;
	cout<<"Επίλεξε μια λειτουργία (0-3): ";
}

int main(){
	int choice=-1;
	while(choice!=0){
		showMenuPart4();
		cin>>choice;
		if(cin.fail()){
			cin.clear();
			cin.ignore(10000,'\n');
			cout<<"Εισάγετε έναν έγκυρο αριθμό."<<endl;
			continue;
		}
		switch(choice){
			case 1:
				testCacheLocality();
				break;
			case 2:
				testFlatVsPointerArray();
				break;
			case 3:
				testFunctionCallSpeed();
				break;
			case 0:
				cout<<"Ολοκλήρωση!"<<endl;
				break;
			default:
				cout<<"Μη έγκυρη επιλογή. Προσπάθησε ξανά."<<endl;
				break;
		}
	}
	return 0;
}

