/*
Nome: Mario
Cognome: Montanari
Classe: 3AIN
Data: 09/05/2025

1) Leggere il primo pixel (BGR);
2) Modificarlo;
3) Elaborare un’immagine (crearla con paint con dimensioni in pixel multiple di 4):
      - Rendendo i pixel casuali;
      - Generando blocchi uniformi di colore;
4) Inventare un banale algoritmo di compressione e decompressione:
      - stessi header poi … i prossimi 50 px sono verdi, i prossimi 20 px sono blu, …
5) Steganografia:
      - Nascondere i byte corrispondenti ai caratteri di una parola sparpagliandoli
	    nei pixel secondo una mappa prestabilita;
      - Nascondere i bit corrispondenti al byte di una o più lettere mascherandoli
	    come lsbit dei pixel secondo una mappa prestabilita;
      - Decomprimere un file bmp compresso secondo un algoritmo banale (vedi es 4.e).
*/

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

#pragma pack(1)

typedef struct {
	char bfType[2];
	int bfSize;
	int bfReserved;
	int bfOffBits;
} BITMAPFILEHEADER;

typedef struct {
	unsigned char b;
	unsigned char g;
	unsigned char r;
} BGR;

void compressioneBMP(const char * inputFilename, const char * outputFilename, int lunghezza, int altezza, int offset);
void decompressioneBMP(const char * inputFilename, const char * outputFilename, int lunghezza, int altezza, int offset);
void nascondiByteNeiPixel(const char * inputFile, const char * outputFile, const char * messaggio, int offset);
void estraiByteDaiPixel(const char * filename, int offset, int intervallo, int lunghezzaMessaggio);
void nascondiBitNeiPixel(const char * inputFilename, const char * outputFilename, const char * messaggio, int offset);
void estraiBitDaiPixel(const char * filename, int offset, int lunghezzaMessaggio);

int main(void) {
	cout << "sizeof(BITMAPFILEHEADER): " << sizeof(BITMAPFILEHEADER) << endl;
	
	FILE * file = fopen("tuttaBianca.bmp", "rb");
	
	if (file != NULL) {
		srand(time(NULL));
		
		int bfOffBits;
		
		BITMAPFILEHEADER bf;
		
		// Scrivo l'header del file .bmp
		fseek(file, 0, SEEK_SET);
		fread(&bf, sizeof(bf), 1, file);
		cout << "bfType: " << bf.bfType[0] << bf.bfType[1] << endl;
		
		fseek(file, 2, SEEK_CUR);
		cout << "bfSize: " << bf.bfSize << endl;
		
		fseek(file, 10, SEEK_CUR);
		cout << "bfOffBits: " << bf.bfOffBits << endl;
		
		fseek(file, bf.bfOffBits, SEEK_SET);
		
		
		// --------------------------------------------------------------------
		// 1. Copiare il file BMP originale in modificato.bmp
		// --------------------------------------------------------------------
		
		rewind(file);
		
		FILE * file_mod = fopen("modificato.bmp", "wb");
		
		if (file_mod != NULL) {
			char buffer[1024];
			int byte;
			
			while ((byte = fread(buffer, 1, sizeof(buffer), file)) > 0) {
				fwrite(buffer, 1, byte, file_mod);
			}
			
			fclose(file_mod);
		} else {
			perror("Error");
		}
		
		
		// --------------------------------------------------------------------
		// 2. Modificare il primo pixel in rosso nel file modificato.bmp
		// --------------------------------------------------------------------
		
		file_mod = fopen("modificato.bmp", "r+b");
		
		if (file_mod != NULL) {
			fseek(file_mod, bf.bfOffBits, SEEK_SET);
			
			BGR bgr;
			
			bgr.b = 255;
			bgr.g = 0;
			bgr.r = 0;
			
			fwrite(&bgr, sizeof(BGR), 1, file_mod);
			
			fclose(file_mod);
		} else {
			perror("Error");
		}
		
		
		// --------------------------------------------------------------------
		// 3. Creare il file modificato2.bmp e riempire con pixel casuali
		// --------------------------------------------------------------------
		
		rewind(file);
		
		FILE * file_mod2 = fopen("modificato2.bmp", "wb");
		
		if (file_mod2 != NULL) {
			char buffer[1024];
			int byte;
			
			while ((byte = fread(buffer, 1, sizeof(buffer), file)) > 0) {
				fwrite(buffer, 1, byte, file_mod2);
			}
			
			fclose(file_mod2);
		} else {
			perror("Error");
		}
		
		
		// --------------------------------------------------------------------
		// Sovrascrivere i pixel con valori casuali in modificato2.bmp
		// --------------------------------------------------------------------
		
		file_mod2 = fopen("modificato2.bmp", "r+b");
		
		if (file_mod2 != NULL) {
			fseek(file_mod2, bf.bfOffBits, SEEK_SET);
			
			int lunghezza = 400;
			int altezza = 400;
			int padding = (4 - (lunghezza * 3) % 4) % 4;
			
			fseek(file_mod2, bf.bfOffBits, SEEK_SET);
			
			for (int y = 0; y < altezza; y++) {
				for (int x = 0; x < lunghezza; x++) {
					BGR pixel;
					
					pixel.b = rand() % 256;
					pixel.g = rand() % 256;
					pixel.r = rand() % 256;
					
					fwrite(&pixel, sizeof(BGR), 1, file_mod2);
				}
				
				// Scrivi il padding se necassario
				for (int p = 0; p < padding; p++) {
					fputc(0x00, file_mod2);
				}
			}
			
			fclose(file_mod2);
		} else {
			perror("Error");
		}
		
		cout << endl;
		
		// --------------------------------------------------------------------
		// 4A. Compressione del file modificato2.bmp
		// --------------------------------------------------------------------
		
		compressioneBMP("modificato2.bmp", "compresso.bmpc", 400, 400, bf.bfOffBits);
		
		
		// --------------------------------------------------------------------
		// 4B. Decompressione del file compresso.bmp
		// --------------------------------------------------------------------
		
		decompressioneBMP("compresso.bmpc", "decompresso.bmp", 400, 400, bf.bfOffBits);
		
		cout << endl;
		
		// Steganografia dei byte interi
		nascondiByteNeiPixel("tuttaBianca.bmp", "conMessaggio_byte.bmp", "MONTANARI", bf.bfOffBits);
		estraiByteDaiPixel("conMessaggio_byte.bmp", bf.bfOffBits, 50, 9);
		
		cout << endl;
		
		// Steganografia dei bit
		nascondiBitNeiPixel("tuttaBianca.bmp", "conMessaggio_bit.bmp", "MONTANARI", bf.bfOffBits);
		estraiBitDaiPixel("conMessaggio_bit.bmp", bf.bfOffBits, 9);
		
		fclose(file);
	} else {
		perror("Error (source)");
	}
	
	return 0;
}

void compressioneBMP(const char * inputFilename, const char * outputFilename, int lunghezza, int altezza, int offset) {
	FILE * inputFile = fopen(inputFilename, "rb");
	FILE * outputFile = fopen(outputFilename, "wb");
	
	if (inputFile != NULL && outputFile != NULL) {
		
		// Copia l'header
		char header[1024];
		
		fread(header, 1, offset, inputFile);
		fwrite(header, 1, offset, outputFile);
		
		
		int padding = (4 - (lunghezza * 3) % 4) % 4;
		
		BGR pixel;
		BGR precedente;
		int count = 0;
		bool primo = true;
		
		// Legge i pixel uno a uno e li comprime
		for (int y = 0; y < altezza; y++) {
			for (int x = 0; x < lunghezza; x++) {
				fread(&pixel, sizeof(BGR), 1, inputFile);
				
				if (primo) {
					precedente = pixel;
					count = 1;
					primo = false;
				} else if (
					pixel.b == precedente.b &&
					pixel.g == precedente.g &&
					pixel.r == precedente.r &&
					count < 255
				) {
					count++;
				} else {
					// Scrive [numero_pixel_uguali][colore]
					fputc(count, outputFile);
					fwrite(&precedente, sizeof(BGR), 1, outputFile);
					precedente = pixel;
					count = 1;
				}
			}
			
			fseek(inputFile, padding, SEEK_CUR);
		}
		
		// Scrive l'ultimo blocco
		if (count > 0) {
			fputc(count, outputFile);
			fwrite(&precedente, sizeof(BGR), 1, outputFile);
		}
		
		cout << "Compressione completata in: " << outputFilename << endl;
		
		fclose(inputFile);
		fclose(outputFile);
	} else {
		perror("Error");
	}
}

void decompressioneBMP(const char * inputFilename, const char * outputFilename, int lunghezza, int altezza, int offset) {
	FILE * inputFile = fopen(inputFilename, "rb");
	FILE * outputFile = fopen(outputFilename, "wb");
	
	if (inputFile != NULL && outputFile != NULL) {
		
		// Leggi e scrivi l'header BMP
		char header[1024];
		
		fread(header, 1, offset, inputFile);
		fwrite(header, 1, offset, outputFile);
		
		int padding = (4 - (lunghezza * 3) % 4) % 4;
		int totalPixels = lunghezza * altezza;
		int writtenPixels = 0;
		
		while (writtenPixels < totalPixels) {
			int count = fgetc(inputFile);
			
			BGR pixel;
			
			fread(&pixel, sizeof(BGR), 1, inputFile);
			
			for (int i = 0; i < count; i++) {
				fwrite(&pixel, sizeof(BGR), 1, outputFile);
				writtenPixels++;
				
				// Dopo ogni riga, aggiungi un padding
				if (writtenPixels % lunghezza == 0) {
					for (int p = 0; p < padding; p++) {
						fputc(0x00, outputFile);
					}
				}
			}
		}
		
		fclose(inputFile);
		fclose(outputFile);
		
		cout << "Decompressione completata in: " << outputFilename << endl;
	} else {
		perror("Error");
	}
}

// Nasconde ogni byte di un messaggio nei colori di pixel a intervalli
void nascondiByteNeiPixel(const char * inputFilename, const char * outputFilename, const char * messaggio, int offset) {
	FILE * inputFile = fopen(inputFilename, "rb");
	FILE * outputFile = fopen(outputFilename, "wb");
	
	if (inputFile != NULL && outputFile != NULL) {
		char header[1024];
		
		fread(header, 1, offset, inputFile);
		fwrite(header, 1, offset, outputFile);
		
		BGR pixel;
		
		int i = 0;
		int intervallo = 50;
		int posizionePixel = 0;
		
		int lunghezza = strlen(messaggio);
		
		while (fread(&pixel, sizeof(BGR), 1, inputFile) == 1) {
			if (posizionePixel % intervallo == 0 && i < lunghezza) {
				pixel.r = messaggio[i++];
			}
			
			fwrite(&pixel, sizeof(BGR), 1, outputFile);
			posizionePixel++;
		}
		
		fclose(inputFile);
		fclose(outputFile);
		
		cout << "Byte nascosti in: " << outputFilename << endl;
	} else {
		perror("Error");
	}
}

// Recupera il messaggio nascosto
void estraiByteDaiPixel(const char * filename, int offset, int intervallo, int lunghezzaMessaggio) {
	FILE * file = fopen(filename, "rb");
	
	if (file != NULL) {
		fseek(file, offset, SEEK_SET);
		
		BGR pixel;
		
		int posizionePixel = 0;
		int letti = 0;
		
		cout << "Messaggio estratto: ";
		while (fread(&pixel, sizeof(BGR), 1, file) == 1 && letti < lunghezzaMessaggio) {
			if (posizionePixel % intervallo == 0) {
				cout << (char)pixel.r;
				
				letti++;
			}
			
			posizionePixel++;
		}
		
		cout << endl;
		
		fclose(file);
	} else {
		perror("Error");
	}
}

// Nasconde bit a bit un messaggio nei pixel
void nascondiBitNeiPixel(const char * inputFilename, const char * outputFilename, const char * messaggio, int offset) {
	FILE * inputFile = fopen(inputFilename, "rb");
	FILE * outputFile = fopen(outputFilename, "wb");
	
	if (inputFile != NULL && outputFile != NULL) {
		char header[1024];
		
		fread(header, 1, offset, inputFile);
		fwrite(header, 1, offset, outputFile);
		
		int posizioneBit = 0;
		int posizioneChr = 0;
		
		BGR pixel;
		
		int len = strlen(messaggio);
		
		while (fread(&pixel, sizeof(BGR), 1, inputFile) == 1) {
			if (posizioneChr < len) {
				char chr = messaggio[posizioneChr];
				int bit = (chr >> (7 - posizioneBit)) & 1;
				
				pixel.b = (pixel.b & 0xFE) | bit;
				
				posizioneBit++;
				
				if (posizioneBit == 8) {
					posizioneBit = 0;
					
					posizioneChr++;
				}
			}
			
			fwrite(&pixel, sizeof(BGR), 1, outputFile);
		}
		
		fclose(inputFile);
		fclose(outputFile);
		
		cout << "Bit nascosti in: " << outputFilename << endl;
	} else {
		perror("Error");
	}
}

// Recupera i bit nascosti nei pixel
void estraiBitDaiPixel(const char * filename, int offset, int lunghezzaMessaggio) {
	FILE * file = fopen(filename, "rb");
	
	if (file != NULL) {
		fseek(file, offset, SEEK_SET);
		
		BGR pixel;
		
		int posizioneBit = 0;
		int chr = 0;
		int estratti = 0;
		
		cout << "Messaggio estratto: ";
		while (fread(&pixel, sizeof(BGR), 1, file) == 1 && estratti < lunghezzaMessaggio) {
			int bit = pixel.b & 1;
			
			chr = (chr << 1) | bit;
			
			posizioneBit++;
			
			if (posizioneBit == 8) {
				cout << (char)chr;
				
				posizioneBit = 0;
				chr = 0;
				
				estratti++;
			}
		}
		
		cout << endl;
		
		fclose(file);
	} else {
		perror("Error");
	}
}