#include "stm32f4xx_hal.h"
#include "globals.h"
#include <string.h>

/*
taille des images 3600 bytes
ajout auto en flash
découpe en 4 sous crc32 simples de 900 bytes
code crc32 algo celui du ethernet

*/

// compare crc image ram avec crc toutes les images en flash
// on sait qu'une image est en flash si le contenu crc est différent de FFFFFFFF x 4
// Utilise les secteurs 6 et 7

#define MAXUINT32 0xFFFFFFFF

// 0x1000 = 4096 > 3600 (RAW_SIZE)
#define IMG_BLOC_SIZE 0x1000


/*
[IMAGE(3600)+4CRC32(16)+IMAGECOUNTER(4)]

446/746 use sectors 6 and 7
Size = 128K for 446 ==> 32 images
Size = 256K for 746 ==> 64 images
*/

#ifdef STM32F446xx
#define SECTOR6 0x08040000
#define SECTOR7 0x08060000
#define SECTOR_SIZE (128*1024)
#endif

//Intensive computations should be located in RAM start 0x00
//746 Flash Access from AXIM interface
#ifdef STM32F746xx
#define SECTOR6 0x08080000
#define SECTOR7 0x080C0000
#define SECTOR_SIZE (256*1024)
#endif


#define IMG_BASE_ADDRESS SECTOR6
#define NB_MAX_IMG_FLASH (SECTOR_SIZE/IMG_BLOC_SIZE)




// variable globale pour les fonctions (selection de secteur)
uint8_t   gflashFlipFlop = 0; // 0 (sector6) ou 1 (sector 7). Init par sécurité
int  	  gfreeSlot;
uint32_t  gminImageCounter;
uint32_t  gmaxImageCounter;


// pas de probleme de perf, code + simple

// returns the bit
uint8_t getBit(uint8_t *data, int bitpos)
{
 if (data[bitpos>>3]&(1<<(bitpos & 0x7))) // extrait bit dans les bytes
   return 1;
 else
   return 0;
}


uint32_t crc(uint8_t *data, int length)
{
  int i;
  static const uint32_t polynome = 0x104C11DB;
  uint32_t shiftRegister;

  shiftRegister = 0xFFFFFFFF;  // Généralement tous les bits à 0 ou 1
  length = length << 3;

	for (i=0;i<length;i++)
	{
		if ((shiftRegister>>31) ^ getBit(data,i))
		{
			shiftRegister = (shiftRegister << 1) ^ polynome;
		}
		else
		{
			shiftRegister = (shiftRegister << 1);
		}
	}
	return shiftRegister;
}

// calcule les 4 crc d'une image
#define CRC_BLOC_SIZE 900
// doit être un entier
#define NB_BLOCS (RAW_SIZE/CRC_BLOC_SIZE)

void compute4crcram(uint8_t *image, uint32_t *fourcrc)
{
	int i;

	for (i=0;i<4;i++)
	{
		fourcrc[i] = crc(&image[900*i],900);
	}
}

// 4crc32 at end of image
uint8_t checkcrc(uint8_t *image, uint32_t *fourcrc)
{
	int i;
	uint32_t *fourcrcimage;

	fourcrcimage = (uint32_t *) &image[RAW_SIZE];
	for (i=0;i<4;i++)
	{
		if (fourcrcimage[i]!=fourcrc[i])
		  return 0;
	}
	return 1;
}



uint8_t *getImageAddress(int i, uint8_t flashFlipFlop)
{
  return (uint8_t *) (IMG_BASE_ADDRESS +  flashFlipFlop*SECTOR_SIZE + IMG_BLOC_SIZE * i );
}

uint32_t getImageCounter(int i, uint8_t flashFlipFlop)
{
	uint32_t *imageCounter;

	imageCounter = (uint32_t *) (IMG_BASE_ADDRESS + flashFlipFlop*SECTOR_SIZE + IMG_BLOC_SIZE * i + RAW_SIZE + 16 );
	return *imageCounter;
}

// extrait de la flash les compteurs image min, max, et le numéro du premier slot libre
// permet de prendre les décisions sur quoi faire d'une nouvelle image
void findMinMaxFreeImageSlot(uint8_t flashFlipFlop, int *freeSlot,  uint32_t *min, uint32_t *max)
{
	int i;
	uint32_t imageCounter;
	int firstfreeSlot = -1;
	uint32_t maxImageCounter = 0; // images will count from 1.
	uint32_t minImageCounter = MAXUINT32;
	int minImageSlot = -1;

	for (i=0;i<NB_MAX_IMG_FLASH;i++)
	{
		imageCounter = getImageCounter(i,flashFlipFlop);
		if (imageCounter!=MAXUINT32)
		{
			if (imageCounter>maxImageCounter)
			 maxImageCounter = imageCounter;
			if (imageCounter<minImageCounter)
			{
			 minImageSlot = i; // extrait l'index
			 minImageCounter = imageCounter; // et le compteur de la plus vielle image
		    }
		}
		else
		{
			if (firstfreeSlot==-1)
				firstfreeSlot = i;
		}
	}
	if (firstfreeSlot==-1) // si aucun slot dispo, prend la plus ancienne image
	{
		firstfreeSlot = minImageSlot;
	}
	// ce code garantit qu'ici on aura jamais firstfreeSlot==-1
	*min = minImageCounter;
	*max = maxImageCounter; // si flash vierge, max == 0;
	*freeSlot = firstfreeSlot;
}

// lis la flash pour déterminer quelle est la derniere banque (celle qui a le plus grand maxCounter)
// les images seront lues de cette banque
// a appeler à chaque boot au minimum
void setFlashFlipFlop(void)
{
	int freeSlot0,freeSlot1;
	uint32_t min0,min1,max0,max1;
	uint8_t flashFlipFlop;

	flashFlipFlop = 0;
	findMinMaxFreeImageSlot(flashFlipFlop, &freeSlot0,&min0,&max0);
	flashFlipFlop = 1;
	findMinMaxFreeImageSlot(flashFlipFlop, &freeSlot1,&min1,&max1);

	if (max1>max0)
	{
	 gflashFlipFlop = 1;
	 gfreeSlot = freeSlot1;
	 gminImageCounter = min1;
	 gmaxImageCounter = max1;
 	}
	else
	{
	 gflashFlipFlop = 0;
	 gfreeSlot = freeSlot0;
	 gminImageCounter = min0;
	 gmaxImageCounter = max0;
 	}

}


int checkIfAlreadyInFlash(uint32_t *fourcrcram)
{
	int i;
	uint8_t *imageflash;

	// même si image vierge, on fait le check, who cares
	for (i=0;i<NB_MAX_IMG_FLASH;i++)
	{
		imageflash = getImageAddress(i,gflashFlipFlop);
		if (checkcrc(imageflash,fourcrcram))
			return 1;
	}
	return 0;
}



void eraseFlashSector(int sector)
{
	FLASH_EraseInitTypeDef eraseInit;
	uint32_t sectorError;

	eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
	eraseInit.Banks = FLASH_BANK_1;
	eraseInit.NbSectors = 1;
	eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; // no high voltage on VPP, so limited to 3 speed
	eraseInit.Sector = sector;

	HAL_FLASH_Unlock();
	HAL_FLASHEx_Erase(&eraseInit, &sectorError);
	HAL_FLASH_Lock();
}

// attention src débordement possible en dehors ram...
// src devrait être en ram et aligné 64 bits
// ne peut pas utiliser des écritures 64 bits car vpp par branché sur la nucleo.
void flash(uint8_t *dst,uint8_t *src,int size)
{
	uint32_t flash;
	uint32_t *ram;

	flash = (uint32_t) dst;
	ram = (uint32_t *) src;

	HAL_FLASH_Unlock();
	while(size>0)
	{
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,flash,*ram);
		ram++;
		flash = flash + 4;
		size = size - 4;
	}
	HAL_FLASH_Lock();
}

// efface l'autre banque
// recopie image par image
// sauf les images vides
// sauf freeSlot
// recopie nouvelle image dans freeslot
void writeFlash(uint8_t flashFlipFlop, int freeSlot, uint8_t *imageram, uint32_t newImageCounter)
{
	int i;
	uint8_t *imageflash;
	// from flashFlipFlop to (1-flashFlipFlop)
	// doit peut-être être aligné 64 bits !
//	#pragma align 8 xxx

	uint8_t tmpImage[IMG_BLOC_SIZE]; // attention, gros bloc là

	if (newImageCounter<=NB_MAX_IMG_FLASH) // no need to flipflop, faster, no sector erase
	{
		// ajoute counter
		memcpy(tmpImage,imageram,RAW_SIZE+16);
		memcpy(&tmpImage[RAW_SIZE+16],(uint8_t *) &newImageCounter,4);

		imageflash = getImageAddress(freeSlot,flashFlipFlop);
		flash(imageflash,tmpImage,RAW_SIZE+16+4);
		return;
	}

	if (flashFlipFlop==0)
		eraseFlashSector(FLASH_SECTOR_7);
	else
		eraseFlashSector(FLASH_SECTOR_6);

	for (i=0;i<NB_MAX_IMG_FLASH;i++)
	{
		if (i==freeSlot)
			continue;
		if (getImageCounter(i,flashFlipFlop)==MAXUINT32)
			continue;
		imageflash = getImageAddress(i,flashFlipFlop);
		memcpy(tmpImage,imageflash,IMG_BLOC_SIZE);
		imageflash = getImageAddress(i,1-flashFlipFlop);
		flash(imageflash,tmpImage,IMG_BLOC_SIZE);
	}

  // 4crc déjà présent, ajoute counter
	memcpy(tmpImage,imageram,RAW_SIZE+16);
	memcpy(&tmpImage[RAW_SIZE+16],(uint8_t *) &newImageCounter,4);

	imageflash = getImageAddress(freeSlot,1-flashFlipFlop);
	flash(imageflash,tmpImage,RAW_SIZE+16+4);
}

// Si aucun slot de libre ?

// Etape 1 : check crc
// si ko stop, même pas affichée
// si ok
// Etape 2 : check si image déjà en flash
// si déjà en flash stop, affiche juste l'image en ram
// sinon
// Si il reste un slot dispo, écrire l'image dedans avec maxCounter+1
// Si aucun slot dispo, ... écrase le plus ancien (l'image minCounter se fait écraser par la nouvelle image avec maxCounter+1)

// Secteur de 128K = 32 images (modes 32..63)
//


// retourne 0 si crc fail
// retourne 1 si image déjà en flash
// retourne 2 si image flashée

int checkUpdateFlash(uint8_t *imageram)
{
	uint32_t fourcrcram[4];


// etape 1
	compute4crcram(imageram,fourcrcram);
	if (checkcrc(imageram,fourcrcram)==0)
	 return 0;
// etape 2
	setFlashFlipFlop(); // détermine gflashFlipFlop,  gfreeSlot et gmaxImageCounter
	if (checkIfAlreadyInFlash(fourcrcram))
	 return 1;
// etape 3/4
	writeFlash(gflashFlipFlop,gfreeSlot,imageram,gmaxImageCounter+1); // makes counter start at 1.

	setFlashFlipFlop();
	return 2;
}



void readImageFromFlash(uint8_t *raw, int imageIdx)
{
	uint8_t *imageflash;
	if ((imageIdx>=NB_MAX_IMG_FLASH)||(imageIdx+1>gmaxImageCounter))
	{
		memset(raw,64,RAW_SIZE); // weak white image
		return;
	}
	
	imageflash = getImageAddress(imageIdx,gflashFlipFlop);
	memcpy(raw,imageflash,RAW_SIZE);
}

