// Datasheet doc: "Atmel-11121-32-bit-Cortex-A5-Microcontroller-SAMA5D3_Datasheet.pdf"

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <pthread.h>
#include <assert.h>
#include "nmap_gpio_screen.h"
#include "ppm.h"

#define XRES  240
#define YRES  320

#define SPIDEVNODE "/dev/spidev32765.0"

#define SPI_CMD_DELAY 30

// 16bpp (RGB 5:6:5) pixel format:
// |15-11|10-05|04-00|
// |  R  |  G  |  B  |

#define COL8TO5(x) (x >> 3)
#define COL8TO6(x) (x >> 2)

#define RGB(r,g,b) ((COL8TO5(r)<<11)|(COL8TO6(g)<<5)|(COL8TO5(b)))

#define GREEN      RGB(0,255,0)
#define GREY       RGB(100,100,100)
#define LIGHT_RED  RGB(255,120,120)

typedef struct {
    int fd_spi;
    struct spi_ioc_transfer cmd;
} spi_display_t;

static uint32_t spi_speed = 12000000;
static uint8_t spi_bits = 8;
static uint32_t spi_mode = 0;
void *gpio_base_addr;
#define write_cmd(cmd) spi_write_cmd(disp, cmd)
#define write_data(data) spi_write_data(disp, data, sizeof(data))

/*
 * Print the global error message and abort execution.
 */
static void pabort(const char *s) {
    perror(s);
    abort();
}

static void spi_write_cmd(spi_display_t *disp, uint8_t cmd) {
    uint8_t unused[4096];

    // one byte for command
    struct spi_ioc_transfer msg = {
            .tx_buf = (unsigned long) &cmd,
            .rx_buf = (unsigned long) unused,
            .len = 1,
            .delay_usecs = SPI_CMD_DELAY,
            .speed_hz = spi_speed,
            .bits_per_word = spi_bits,
    };

    // Set data/cmd pin to 0
  	set_command(0, gpio_base_addr);

    // SPI_IOC_MESSAGE(1) indicates we are sending 1 message (the message "cmd"),
    // but we could send any number of messages at once
    // More details in kernel source: include/uapi/linux/spi/spidev.h
    if (ioctl(disp->fd_spi, SPI_IOC_MESSAGE(1), &msg) == -1)
        pabort("Failed sending spi message");
}

static void spi_write_data(spi_display_t *disp, uint8_t const *data, size_t data_len) {
    uint8_t unused[4096];

    // one byte for command
    struct spi_ioc_transfer msg = {
        .tx_buf = (unsigned long) data,
        .rx_buf = (unsigned long) unused,
        .len = data_len,      // maximum data length is 4096 bytes
        .delay_usecs = SPI_CMD_DELAY,
        .speed_hz = spi_speed,
        .bits_per_word = spi_bits,
    };

    // Set data/cmd pin to 1
	set_command(1, gpio_base_addr);

    // SPI_IOC_MESSAGE(1) indicates we are sending 1 message (the message "cmd"),
    // but we could send any number of messages at once
    // More details in kernel source: include/uapi/linux/spi/spidev.h
    if (ioctl(disp->fd_spi, SPI_IOC_MESSAGE(1), &msg) == -1)
        pabort("Failed sending spi message");
}

static void disp_setwindow(spi_display_t *disp, int x0, int y0, int x1, int y1) {
    assert(!(x1 > XRES-1 || y1 > YRES-1 || x0 > x1 || y0 > y1));

	int YSH = y0 >> 8;
	int YSL = y0;
	int YEH = y1 >> 8;
	int YEL = y1;

    // Set column (left and right zone)
    write_cmd(0x2A);
    { uint8_t dat[] = { 0x00, x0, 0x00, x1 };
    write_data(dat); }

    // Set page address (top and bottom lines)
    write_cmd(0x2B);
    { uint8_t dat[] = { YSH, YSL, YEH, YEL };
    write_data(dat); }

    // Memory write
    write_cmd(0x2C);
}

void disp_clear(spi_display_t *disp, uint16_t color) {
uint8_t sortie[4096];
uint16_t compteur=0;
    disp_setwindow(disp, 0, 0, XRES-1, YRES-1);
    for (int i = 0; i < XRES; i++) {
        for (int j = 0; j < YRES; j++) {
		if(compteur<=4096){
			uint8_t m, n;
            		m = color >> 8;
            		n = color;
			sortie[compteur]=  m;
			sortie[compteur+1]=  n;
			compteur+=2;
		}
		else{
			compteur=0;
			write_data(sortie);
		}

        }
    }
	if(compteur!=0){
 		spi_write_data(disp, sortie,compteur);
	}
}

void disp_setpix(spi_display_t *disp, int x, int y, uint16_t color) {
    disp_setwindow(disp, x, y, x, y);
    uint8_t m, n;
    m = color >> 8;
    n = color;
    { uint8_t dat[] = { m, n };
    write_data(dat); }
}

static void disp_init(spi_display_t *disp) {
    // SPI mode
    if (ioctl(disp->fd_spi, SPI_IOC_WR_MODE32, &spi_mode) == -1)
        pabort("Failed setting spi mode");

    // SPI bits per word
    if (ioctl(disp->fd_spi, SPI_IOC_WR_BITS_PER_WORD, &spi_bits) == -1)
        pabort("Failed setting spi bits per word");

    // SPI max speed in hz
    if (ioctl(disp->fd_spi, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed) == -1)
        pabort("Failed setting spi max speed");

    printf("SPI mode: 0x%x\n", spi_mode);
    printf("SPI bits per word: %d\n", spi_bits);
    printf("SPI max speed: %d Hz (%d KHz)\n", spi_speed, spi_speed / 1000);

    // Initialization sequence for the ILI9341 display (mylab2)
    write_cmd(0x01);   // Software reset
    usleep(50000);     // Wait 50ms

    write_cmd(0x11);
    usleep(120000);    // Wait 120ms

    write_cmd(0xCF);
    { uint8_t dat[] = { 0x00, 0x83, 0x30 };
    write_data(dat); }

 	write_cmd(0xED);
    { uint8_t dat[] = { 0x64, 0x03, 0x12, 0x81 };
    write_data(dat); }

 	write_cmd(0xE8);
    { uint8_t dat[] = { 0x85, 0x01, 0x79 };
    write_data(dat); }

 	write_cmd(0xCB);
    { uint8_t dat[] = { 0x39, 0x2C, 0x00, 0x34, 0x02 };
    write_data(dat); }

 	write_cmd(0xF7);
    { uint8_t dat[] = { 0x20 };
    write_data(dat); }

 	write_cmd(0xEA);
    { uint8_t dat[] = { 0x00, 0x00 };
    write_data(dat); }

 	write_cmd(0xC1);             // Power control
    { uint8_t dat[] = { 0x11 };  // SAP[2:0];BT[3:0]
    write_data(dat); }

 	write_cmd(0xC5);    // VCM control 1
    { uint8_t dat[] = { 0x34, 0x3D };
    write_data(dat); }

 	write_cmd(0xC7);    // VCM control 2
    { uint8_t dat[] = { 0xC0 };
    write_data(dat); }

 	write_cmd(0x36);    // Memory Access Control
    { uint8_t dat[] = { 0x08 };
    write_data(dat); }

 	write_cmd(0x3A);   	         // Pixel format
    { uint8_t dat[] = { 0x55 };  // 16bpp
    write_data(dat); }

 	write_cmd(0xB1);   	               // Frame rate
    { uint8_t dat[] = { 0x00, 0x1D };  // 65Hz
    write_data(dat); }

 	write_cmd(0xB6);    // Display Function Control
    { uint8_t dat[] = { 0x0A, 0xA2, 0x27, 0x00 };
    write_data(dat); }

	write_cmd(0xb7);    // Entry mode
    { uint8_t dat[] = { 0x07 };
    write_data(dat); }

 	write_cmd(0xF2);    // 3Gamma Function Disable
    { uint8_t dat[] = { 0x08 };
    write_data(dat); }

 	write_cmd(0x26);    // Gamma curve selected
    { uint8_t dat[] = { 0x01 };
    write_data(dat); }

	write_cmd(0xE0);    // positive gamma correction
    { uint8_t dat[] = { 0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0x87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00 };
    write_data(dat); }

	write_cmd(0xE1);    // negamma correction
    { uint8_t dat[] = { 0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F };
    write_data(dat); }

 	write_cmd(0x11);    // Exit sleep
 	usleep(120000);

 	write_cmd(0x29);    // Display on
 	usleep(50000);

    // Set backlight pin to 1
 	set_backlight(1, gpio_base_addr);
}
void display_image(img_t *image_ppm,spi_display_t *disp)
{
  // pixel_t tmp;
  // for (int x = 0; x < image->width; x++){
  //   for (int y = 0; y < image->height; y++){
  //     tmp = image->pix2d[y][x];
  //     disp_setpix(disp,x,y,RGB(tmp.r,tmp.g,tmp.b),gpio_base_addr);
  //   }
  // }
  int compteur = 0;
  uint8_t sortie[4096];
  uint8_t m, n;
  uint16_t color_rgb;
disp_setwindow(disp, 0, 0, XRES-1, YRES-1);
  for (int i = 0; i <  image_ppm->height; i++) {
      for (int j = 0; j < image_ppm->width; j++) {
          pixel_t couleur=image_ppm->pix2d[i][j];
	  color_rgb=RGB(couleur.r,couleur.g,couleur.b);
          m = color_rgb >> 8;
          n = color_rgb;
          sortie[compteur]=  m;
	  sortie[compteur+1]=  n;
          compteur+=2;
          if(compteur>=4096)
          {
            write_data(sortie);
            compteur=0;
          }
      }
  }
  if(compteur !=0)
  {
   spi_write_data(disp,sortie,compteur);
  }
}

int main(int argc , char *argv[]) {

	int fd;
	int signal;
	spi_display_t disp;
  int signal_recu;
	img_t *image_ppm = NULL;
	sigset_t signal_set;
  if ((disp.fd_spi = open(SPIDEVNODE, O_RDWR)) == -1)
        pabort("Failed opening SPI device");
  init_pin_screen(&fd, &gpio_base_addr);
  set_backlight(0, gpio_base_addr);
  set_command(0, gpio_base_addr);
  disp_init(&disp);
  printf("screen ready\n");
  sigemptyset(&signal_set);
	sigaddset(&signal_set,SIGINT);
	sigaddset(&signal_set,SIGUSR1);
	pthread_sigmask(SIG_SETMASK, &signal_set, NULL);
  disp_clear(&disp, GREY);
	while(1){
		signal_recu=sigwait(&signal_set,&signal);
		if(signal_recu==0){
        if(signal==SIGUSR1){
          image_ppm = load_ppm("FLASK/to_display/img_to_disp.ppm");
          if(image_ppm!=NULL){
		          FILE *disp_display=fopen("FLASK/to_display/wait.txt","r");
		            if(!disp_display)
		             {
                      printf("affichage image\n");
 			                disp_display = fopen("FLASK/to_display/wait.txt", "w");
    			            disp_clear(&disp, GREY);
			                display_image(image_ppm,&disp);
                      remove ("FLASK/to_display/wait.txt");
                      remove ("FLASK/to_display/img_to_disp.ppm");
                }
		      }
          else{
              printf("image indisponible\n");
          }
        }
        else
    		{
    				break;
    		}
      }
    	else
    	{
    		break;
    	}
  }
  set_backlight(0, gpio_base_addr);
  set_command(0, gpio_base_addr);
  free_pin_screen(fd, gpio_base_addr);
  close(disp.fd_spi);
  printf("programme termine\n");
}
