#include <fstream>
using std::ofstream;
using std::ios;

#include "BMP.h"

#include "type_image.h"

//////////////////////////////////////////////////////////////////
//                                                              //
//  Cette fonction sert à écrire le résultat de la matrice des  //
//  couleurs en format .bmp si l'image a 4 bits par pixels      //
//                                                              //
////////////////////////////////////////////////////////////////// 

bool SauveBMP1(const string& NomFic, const type_couleur CoulTable[], int NbCouleurs,
			   unsigned char Indice[], unsigned char** Image, int Width, int Height)
{
	int i, j;

	bmpinfoheader bmih;
	bmpfileheader bmfh;

	unsigned int extrabytes, bytesize; 

	char *PtrTemp; // Pointeur servant à ce que chaque ligne ait 32 bits.

	// sert à rajouter des bytes pour que la largeur soit un multiple de 4
	extrabytes = (4 - ((Width + 7) / 8) % 4) % 4;

	// Grandeur de l'image
	bytesize = ((Width + 7)/8 + extrabytes) * Height;

	bmfh.bfType = 'MB';
	bmfh.bfSize = 0;
	bmfh.bfReserved1 = 0;
	bmfh.bfReserved2 = 0;
	bmfh.bfOffBits = (3*sizeof(unsigned short)+2*sizeof(unsigned long)) + sizeof(bmpinfoheader) +
		             4*NbCouleurs*sizeof(char);

	bmih.biSize = sizeof(bmpinfoheader);
	bmih.biWidth = Width;
	bmih.biHeight = Height;
	bmih.biPlanes = 1;
	bmih.biBitCount = 1;
	bmih.biCompression = 0L;
	bmih.biSizeImage = bytesize;
	bmih.biXPelsPerMeter = 0;
	bmih.biYPelsPerMeter = 0;
	bmih.biClrUsed = (NbCouleurs == 2) ? 0:NbCouleurs;
	bmih.biClrImportant = 0;


	// Ecriture du fichier

	ofstream FicImage;

	FicImage.open(NomFic.c_str(), ios::binary);

	if (FicImage.fail())
		return false;


	// Écriture du bmpfileheader, qu'on doit écrire membre à membre car la structure prend
	// 16 bytes alors que la somme de chacun de ses membres est 14 bytes.
	FicImage.write( (char *) &bmfh.bfType, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfSize, sizeof(unsigned long));
	FicImage.write( (char *) &bmfh.bfReserved1, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfReserved2, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfOffBits, sizeof(unsigned long));
	
	FicImage.write( (char *) &bmih, sizeof(bmpinfoheader));


	// Écriture de la table de couleurs
	unsigned char Fill = 0;

	for (i = 0; i < NbCouleurs; i++)
	{
		FicImage.write( (char *) &CoulTable[i], 3*sizeof(unsigned char));
		FicImage.write( (char *) &Fill, sizeof(unsigned char));
	}


	// On alloue ensuite de la mémoire à un pointeur bidon, pour compléter les lignes,
	// pour qu'elles contiennent des multiples de 4.

	PtrTemp = new char[extrabytes];
	if (PtrTemp == 0)
		return false;


	unsigned char Towrite = 0;

	for (j = Height; j > 0; j--)
	{
		for (i = 0; i < Width; i++)
		{
			Towrite ^= Indice[Image[i][j-1]] << (7 - (i % 8));

			if (!( (i + 1) % 8))
			{
				FicImage.write( (char *) &Towrite, sizeof(unsigned char));
				Towrite = 0;
			}
		}

		if (Width % 8)
			FicImage.write( (char *) &Towrite, sizeof (unsigned char));

		FicImage.write( (char *) &PtrTemp, extrabytes*sizeof(char));
	}

	delete []PtrTemp;

	FicImage.close();

	return true;
}




//////////////////////////////////////////////////////////////////
//                                                              //
//  Cette fonction sert à écrire le résultat de la matrice des  //
//  couleurs en format .bmp si l'image a 4 bits par pixels      //
//                                                              //
////////////////////////////////////////////////////////////////// 

bool SauveBMP4(const string& NomFic, const type_couleur CoulTable[], int NbCouleurs,
			   unsigned char Indice[], unsigned char** Image, int Width, int Height)
{
	int i, j;

	bmpinfoheader bmih;
	bmpfileheader bmfh;

	unsigned int extrabytes, bytesize; 

	char *PtrTemp; // Pointeur servant à ce que chaque ligne ait 32 bits.

	// sert à rajouter des bytes pour que la largeur soit un multiple de 4
	extrabytes = (4 - ((Width + 1) / 2) % 4) % 4;

	// Grandeur de l'image
	bytesize = ((Width + 1)/2 + extrabytes) * Height;

	bmfh.bfType = 'MB';
	bmfh.bfSize = 0;
	bmfh.bfReserved1 = 0;
	bmfh.bfReserved2 = 0;
	bmfh.bfOffBits = (3*sizeof(unsigned short)+2*sizeof(unsigned long)) + sizeof(bmpinfoheader) +
		             4*NbCouleurs*sizeof(char);

	bmih.biSize = sizeof(bmpinfoheader);
	bmih.biWidth = Width;
	bmih.biHeight = Height;
	bmih.biPlanes = 1;
	bmih.biBitCount = 4;
	bmih.biCompression = 0L;
	bmih.biSizeImage = bytesize;
	bmih.biXPelsPerMeter = 0;
	bmih.biYPelsPerMeter = 0;
	bmih.biClrUsed = (NbCouleurs == 16) ? 0:NbCouleurs;
	bmih.biClrImportant = 0;


	// Ecriture du fichier

	ofstream FicImage;

	FicImage.open(NomFic.c_str(), ios::binary);

	if (FicImage.fail())
		return false;


	// Écriture du bmpfileheader, qu'on doit écrire membre à membre car la structure prend
	// 16 bytes alors que la somme de chacun de ses membres est 14 bytes.
	FicImage.write( (char *) &bmfh.bfType, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfSize, sizeof(unsigned long));
	FicImage.write( (char *) &bmfh.bfReserved1, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfReserved2, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfOffBits, sizeof(unsigned long));
	
	FicImage.write( (char *) &bmih, sizeof(bmpinfoheader));


	// Écriture de la table de couleurs
	unsigned char Fill = 0;

	for (i = 0; i < NbCouleurs; i++)
	{
		FicImage.write( (char *) &CoulTable[i], 3*sizeof(unsigned char));
		FicImage.write( (char *) &Fill, sizeof(unsigned char));
	}


	// On alloue ensuite de la mémoire à un pointeur bidon, pour compléter les lignes,
	// pour qu'elles contiennent des multiples de 4.

	PtrTemp = new char[extrabytes];
	if (PtrTemp == 0)
		return false;


	unsigned char Towrite;

	for (j = Height; j > 0; j--)
	{
		for (i = 0; i < Width; i++)
		{
			if (i % 2)
			{
				Towrite ^= Indice[Image[i][j-1]];
				FicImage.write( (char *) &Towrite, sizeof(unsigned char));
			}
			else
			{
				Towrite = (Indice[Image[i][j-1]]) << 4;
			}
		}

		if (Width % 2)
				FicImage.write( (char *) &Towrite, sizeof(unsigned char));

		FicImage.write( (char *) &PtrTemp, extrabytes*sizeof(char));
	}

	delete []PtrTemp;

	FicImage.close();

	return true;
}




//////////////////////////////////////////////////////////////////
//                                                              //
//  Cette fonction sert à écrire le résultat de la matrice des  //
//  couleurs en format .bmp si l'image a 8 bits par pixels      //
//                                                              //
////////////////////////////////////////////////////////////////// 

bool SauveBMP8(const string& NomFic, const type_couleur CoulTable[], int NbCouleurs,
			   unsigned char Indice[], unsigned char** Image, int Width, int Height)
{
	int i, j;

	bmpinfoheader bmih;
	bmpfileheader bmfh;

	unsigned int extrabytes, bytesize; 

	char *PtrTemp; // Pointeur servant à ce que chaque ligne ait 32 bits.

	// sert à rajouter des bytes pour que la largeur soit un multiple de 4
	extrabytes = (4 - Width % 4) % 4;

	// Grandeur de l'image
	bytesize = (Width + extrabytes) * Height;

	bmfh.bfType = 'MB';
	bmfh.bfSize = 0;
	bmfh.bfReserved1 = 0;
	bmfh.bfReserved2 = 0;
	bmfh.bfOffBits = (3*sizeof(unsigned short)+2*sizeof(unsigned long)) + sizeof(bmpinfoheader) +
		             4*NbCouleurs*sizeof(char);

	bmih.biSize = sizeof(bmpinfoheader);
	bmih.biWidth = Width;
	bmih.biHeight = Height;
	bmih.biPlanes = 1;
	bmih.biBitCount = 8;
	bmih.biCompression = 0L;
	bmih.biSizeImage = bytesize;
	bmih.biXPelsPerMeter = 0;
	bmih.biYPelsPerMeter = 0;
	bmih.biClrUsed = (NbCouleurs == 256) ? 0:NbCouleurs;
	bmih.biClrImportant = 0;


	// Ecriture du fichier

	ofstream FicImage;

	FicImage.open(NomFic.c_str(), ios::binary);

	if (FicImage.fail())
		return false;


	// Écriture du bmpfileheader, qu'on doit écrire membre à membre car la structure prend
	// 16 bytes alors que la somme de chacun de ses membres est 14 bytes.
	FicImage.write( (char *) &bmfh.bfType, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfSize, sizeof(unsigned long));
	FicImage.write( (char *) &bmfh.bfReserved1, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfReserved2, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfOffBits, sizeof(unsigned long));
	
	FicImage.write( (char *) &bmih, sizeof(bmpinfoheader));


	// Écriture de la table de couleurs
	unsigned char Fill = 0;

	for (i = 0; i < NbCouleurs; i++)
	{
		FicImage.write( (char *) &CoulTable[i], 3*sizeof(unsigned char));
		FicImage.write( (char *) &Fill, sizeof(unsigned char));
	}


	// On alloue ensuite de la mémoire à un pointeur bidon, pour compléter les lignes,
	// pour qu'elles contiennent des multiples de 4.

	PtrTemp = new char[extrabytes];
	if (PtrTemp == 0)
		return false;

	for (j = Height; j > 0; j--)
	{
		for (i = 0; i < Width; i++)
			FicImage.write( (char *) &Indice[Image[i][j-1]], sizeof(unsigned char));

		FicImage.write( (char *) &PtrTemp, extrabytes*sizeof(char));
	}

	delete []PtrTemp;

	FicImage.close();

	return true;
}




//////////////////////////////////////////////////////////////////
//                                                              //
//  Cette fonction sert à écrire le résultat de la matrice des  //
//  couleurs en format .bmp si l'image a 24 bits par pixels     //
//                                                              //
////////////////////////////////////////////////////////////////// 

bool SauveBMP24(const string& NomFic, const type_image& Image)
{
	bmpinfoheader bmih;
	bmpfileheader bmfh;

	unsigned int extrabytes, bytesize; 

	char *PtrTemp; // Pointeur servant à ce que chaque ligne ait 32 bits.

	// sert à rajouter des bytes pour que la largeur soit un multiple de 4
	extrabytes = (4 - (Image.Width * 3) % 4) % 4;

	// Grandeur de l'image
	bytesize = (Image.Width * 3 + extrabytes) * Image.Height;

	bmfh.bfType = 'MB';
	bmfh.bfSize = 0;
	bmfh.bfReserved1 = 0;
	bmfh.bfReserved2 = 0;
	bmfh.bfOffBits = (3*sizeof(unsigned short)+2*sizeof(unsigned long)) + sizeof(bmpinfoheader);

	bmih.biSize = sizeof(bmpinfoheader);
	bmih.biWidth = Image.Width;
	bmih.biHeight = Image.Height;
	bmih.biPlanes = 1;
	bmih.biBitCount = 24;
	bmih.biCompression = 0L;
	bmih.biSizeImage = bytesize;
	bmih.biXPelsPerMeter = 0;
	bmih.biYPelsPerMeter = 0;
	bmih.biClrUsed = 0;
	bmih.biClrImportant = 0;


	// Ecriture du fichier

	ofstream FicImage;

	FicImage.open(NomFic.c_str(), ios::binary);

	if (FicImage.fail())
		return false;


	// Écriture du bmpfileheader, qu'on doit écrire membre à membre car la structure prend
	// 16 bytes alors que la somme de chacun de ses membres est 14 bytes.
	FicImage.write( (char *) &bmfh.bfType, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfSize, sizeof(unsigned long));
	FicImage.write( (char *) &bmfh.bfReserved1, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfReserved2, sizeof(unsigned short));
	FicImage.write( (char *) &bmfh.bfOffBits, sizeof(unsigned long));
	
	FicImage.write( (char *) &bmih, sizeof(bmpinfoheader));


	// On alloue ensuite de la mémoire à un pointeur bidon, pour compléter les lignes,
	// pour qu'elles contiennent des multiples de 4.

	PtrTemp = new char[extrabytes];
	if (PtrTemp == 0)
		return false;

	
	unsigned char CouleurTemp[3];
	
	// Ecriture des couleurs (b, g, r), LIGNE par LIGNE, du coin inférieur gauche au coin
	// supérieur droit

	for (int j = Image.Height; j > 0; j--)
	{
		for (int i = 0; i < Image.Width; i++)
		{
			// Les const_cast<> sont là parce que la fonction est const
			CouleurTemp[0] = (unsigned char) ((*(const_cast<type_image *>(&Image)))(i, j-1, BLEU));
			CouleurTemp[1] = (unsigned char) ((*(const_cast<type_image *>(&Image)))(i, j-1, VERT));
			CouleurTemp[2] = (unsigned char) ((*(const_cast<type_image *>(&Image)))(i, j-1, ROUGE));
			FicImage.write( (char *) &CouleurTemp, 3*sizeof(char));
		}
		FicImage.write( (char *) &PtrTemp, extrabytes*sizeof(char));
	}
	delete []PtrTemp;

	FicImage.close();

	return true;
}




///////////////////////////////////////////////////////////////////////
//                                                                   //
//  Cette fonction sert à trier la table des couleurs.               //
//                                                                   //
/////////////////////////////////////////////////////////////////////// 

int Partition(type_couleur Couleur[], unsigned char Indice[], unsigned inf, unsigned sup)
{
	type_couleur Pivot, CoulTemp;
	unsigned char Tampon;
	int i, j;

	Pivot = Couleur[(sup+inf)/2];
	i = inf - 1;
	j = sup + 1;

	// Tant que les indices ne se croisent pas
	while (i < j)
	{
		// Conserver les éléments plus petits ou égaux au pivot à gauche
		do
		{
			i++;
		}
		while (Couleur[i].Frequence > Pivot.Frequence);

		// Conserver les éléments plus grands ou égaux au pivot à droite
		do
		{
			j--;
		}
		while (Couleur[j].Frequence < Pivot.Frequence);

		// Permuter les éléments qui ne sont pas à leur place
		if (i < j)
		{
			CoulTemp = Couleur[i];
			Couleur[i] = Couleur[j];
			Couleur[j] = CoulTemp;

			Tampon = Indice[i];
			Indice[i] = Indice[j];
			Indice[j] = Tampon;
		}
	}

	return j;
}

void Tri(type_couleur Couleur[], unsigned char Indice[], unsigned char inf, unsigned char sup)
{
	unsigned char milieu;

	if (sup > inf)	// S'il y a au moins 2 éléments
	{
		milieu = Partition(Couleur, Indice, inf, sup);

		// Trier la partie de gauche et la partie de droite
		Tri(Couleur, Indice, inf, milieu);
		Tri(Couleur, Indice, milieu+1, sup);
	}
}




///////////////////////////////////////////////////////////////////////
//                                                                   //
//  Cette fonction sert à écrire le résultat de la matrice des       //
//  couleurs en format .bmp et retourne vrai en cas de succès, faux  //
//  autrement.                                                       //
//                                                                   //
/////////////////////////////////////////////////////////////////////// 

bool type_image::SauveBMP(const string& NomFic) const
{
	int i, j, k, NbCouleurs = 0;
	type_couleur CoulTemp;
	bool Trouve;

	
	// On doit commencer par déterminer le nombre de couleurs différentes dans l'image pour
	// voir si on peut stocker l'image avec une table de couleurs

	type_couleur CoulTable[256];
	unsigned char Indice[256];
	unsigned char IndiceTemp[256];

	unsigned char **Image = new unsigned char*[Width];
	if (!Image)
		return false;
	for (i = 0; i < Width; i++)
	{
		Image[i] = new unsigned char[Height];
		if (!Image[i])
		{
			for (j = i; j > 0; j--)
				delete [] Image[i];
			return false;
		}
	}

	for (i = 0; i < Width; i++)
	{
		for (j = 0; j < Height; j++)
		{
			CoulTemp.Rouge = (*(const_cast<type_image *>(this)))(i, j, ROUGE);
			CoulTemp.Vert = (*(const_cast<type_image *>(this)))(i, j, VERT);
			CoulTemp.Bleu = (*(const_cast<type_image *>(this)))(i, j, BLEU);

			Trouve = false;

			for (k = 0; (k < NbCouleurs) && (!Trouve); k++)
				if (CoulTemp == CoulTable[k])
				{
					CoulTable[k].Frequence += 1;
					Image[i][j] = k;
					Trouve = true;

				}

			if (!Trouve)
			{
				NbCouleurs++;
				if (NbCouleurs == 257)
					break;
				CoulTable[NbCouleurs-1] = CoulTemp;
				CoulTable[NbCouleurs-1].Frequence = 1;
				Image[i][j] = NbCouleurs - 1;
			}
		}

		if (NbCouleurs == 257)
			break;
	}

	unsigned char biBitCount;

	if (NbCouleurs < 3)
		biBitCount = 1;
	else if (NbCouleurs < 17)
		biBitCount = 4;
	else if (NbCouleurs < 257)
		biBitCount = 8;
	else
		biBitCount = 24;


	if (biBitCount != 24) // On tri les couleurs par ordre d'importance
	{
		for (i = 0; i < NbCouleurs; i++)
			IndiceTemp[i] = i;

		Tri(CoulTable, IndiceTemp, 0, NbCouleurs - 1);

		// On d'avoir un tableau qui nous donne l'indice placé à un rang qu'on connaît,
		// on veut un tableau qui nous donne la position de l'indice (qui est dans Image[][])
		// Le tableau d'indice permettra donc d'accéder à la table de couleurs;

		for (i = 0; i < NbCouleurs; i++)
			Indice[IndiceTemp[i]] = i;
	}


	bool Retour;

	switch (biBitCount)
	{
	case (1): Retour = SauveBMP1(NomFic, CoulTable, NbCouleurs, Indice, Image, Width, Height);
		      break;
	case (4): Retour = SauveBMP4(NomFic, CoulTable, NbCouleurs, Indice, Image, Width, Height);
		      break;
	case (8): Retour = SauveBMP8(NomFic, CoulTable, NbCouleurs, Indice, Image, Width, Height);
		      break;
	case (24): Retour = SauveBMP24(NomFic, *this); break;
	default: Retour = false; break;
	}

	for (i = 0; i < Width; i++)
		delete [] Image[i];
	delete [] Image;

	return Retour;
}
