
// Description: Serveur Node.js pour le site web de l'IA
// Auteur: Gaëtan Corabœuf et Ugo Maiurano
// Date: 2023
// Version: 1.0

// Importer les modules 
import express from 'express'   // For http server
// Start express
const app = express();
import path from 'path';    // For getting path to the public folder
import http from 'http';
const server = http.createServer(app);
import net from 'net';
import { EventEmitter } from 'events';
const emitter = new EventEmitter()
const pythonSocketServer = net.createServer();
import { Server } from 'socket.io';
const io = new Server(server);   // For getting path to the public folder
import fs from 'fs';    // For getting path to the public folder
import { Buffer } from 'buffer';
const buf = Buffer.from('buffer');
import events from 'events';
events.EventEmitter.defaultMaxListeners = 0;
// turn off limits by default (BE CAREFUL)
emitter.setMaxListeners(0);
// Get current path and server static pages from the public folder
app.use(express.static('public'));

// variables globales
var image_IA_nb = 1;
var requests = [];
var id = 0;
let buffer_mem = Buffer.alloc(0);
let buffer_msgToSend = []; // buffer de messages à envoyer
let received_from_python = 0;
let received_connection = 0;  // 1 = connection reçue, 0 = connection non reçue
let demande_test_connection = 0;  // 1 = demande de test de connection reçue, 0 = demande de test de connection non reçue
let utilisateur = 0;
let utilisateur_read = 0;
let buffer_prompts = [];
let GPU_disponible = 1; // 1 = disponible, 0 = non disponible
let flag_received_seed = 1; // 1 = seed non reçu, 0 = seed reçu
let flag_received_utilisateur = 1; // 1 = utilisateur non reçu, 0 = utilisateur reçu
let size = 0;
let received = 0;
let chunks = [];


// Start the server
// Quand un client se connecte, on le note dans la console,
// et on lui envoie un message pour lui indiquer si le serveur python est connecté
io.on('connection', (socket) => {
  console.log('a user connected');

  socket.on('data', (msg) => {
    console.log('data :', msg);
  });

  socket.on('message', (msg) => {
    console.log(msg);
  });

  io.on('connection', (socket) => {
    if (received_connection == 1) {
      socket.emit('connection', 1);
      console.log('Connection testée avec succès');
    } else {
      socket.emit('connection', 0);
      console.log('Connection défaillante');
    }
  });
  socket.on('disconnect', () => {
    console.log('user disconnected');
  });
});


server.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});

// Start the python socket server
pythonSocketServer.listen(3001, () => {
  console.log('Python Socket Server started on http://localhost:3001');
});

pythonSocketServer.on('connection', (socket_python) => {
  console.log('Python Socket Server connected');
  console.log(demande_test_connection)
  received_connection = 1; // le serveur python est connecté
  // Start the server
  io.on('connection', (socket) => {
    // Réception des données
    // Réception de la taille du fichier
    socket_python.on('data', (msg) => {
      // si le serveur python nous dit bonjour on affiche bonjour
      if (msg.toString() == 'Bonjour, serveur!') {
        console.log('Bonjour de Python');
      }
      // si le serveur python nous dit qu'il est prêt on affiche prêt
      else if (msg.toString() == 'test_connection') {
        console.log('test_connection');
        demande_test_connection = 1;
      }
      // si le serveur python nous dit gue le GPU est disponible on affiche GPU disponible
      // et on envoie le prompt de l'utilisateur suivant si il y en a un
      else if (msg.toString() == 'GPU disponible' && GPU_disponible == 1) {//si le GPU est disponible
        console.log('GPU disponible');
        console.log('utilisateur_read +1 :', utilisateur_read + 1, "id :", id);
        if (utilisateur_read + 1 <= id) {//si il y a un utilisateur en attente
          socket_python.write(JSON.stringify(buffer_prompts[utilisateur_read + 1]));
          console.log('GPU relancé par l\'utilisateur:', utilisateur_read + 1);
          for (let i = utilisateur_read; i <= id; i++) {
            requests[i].emit('position_queue', id - utilisateur_read);//envoi de la position des autres utilisateurs dans la queue (en attente)
          }
          GPU_disponible = 0; // le GPU n'est plus disponible (pour éviter de relancer le GPU car le buffer de la trame n'est pas forcément vide et on pourrait envoyer pluseurs fois le même prompt)

        }
      }
      // si le serveur python nous dit qu'il a fini de générer une image on l'envoie au client qu'il l'a demandé
      else if (msg.toString().split(':')[0] == 'utilisateur' && flag_received_utilisateur == 1) {
        utilisateur_read = parseInt(msg.toString().split(':')[1]);
        console.log('utilisateur reçu :', msg.toString().split(':')[1]);
        image_IA_nb = 1;
        //envoi des images
        for (let i = 0; i <= buffer_prompts[utilisateur_read].NB_img; i++) {
          // console.log('numéro de l\'image envoyée :', buffer_msgToSend[i+1].id);
          // console.log('buffer_msgToSend[i].data :', buffer_msgToSend[i].data);
          requests[utilisateur_read].emit('image', buffer_msgToSend[i]);
        }
        flag_received_utilisateur = 0;
      }
      // on reçoit la seed du serveur python 
      // (car si l'utilisateur a demandé une seed random, le serveur python lui en génère une)
      // puis on renvoie le prompt qu'a demandé l'utilisateur pour q'il puisse l'afficher
      else if (msg.toString().split(':')[0] == 'seed' && flag_received_seed == 1) {
        console.log('seed reçu :', msg.toString().split(':')[1]);
        buffer_prompts[utilisateur_read].Seed = msg.toString().split(':')[1];
        socket.emit('retour_info', buffer_prompts[utilisateur_read]);
        //console.log('buffer_prompts[utilisateur_read] :', buffer_prompts[utilisateur_read]);
        //nettoyage des images
        // for (let i = 0; i <= buffer_prompts[utilisateur_read].NB_img; i++) {
        //   fs.rm(`public/${buffer_msgToSend[i]}`,{recursive:true}, (err) => {
        //     if (err) {
        //       console.log('erreur lors de la suppression des images');
        //     }else{
        //       console.log('images supprimées');
        //     }
        //   }
        //   )
        // }
        // nettoyage du buffer
        // buffer_prompts[utilisateur_read] = [];
        // for (let i = 0; i <= id; i++) {
        //   buffer_prompts[i] = buffer_prompts[i + 1];
        //   requests[i] = requests[i + 1];
        // }
        // utilisateur_read--;
        // id--;
        // console.log('buffer nettoyé');
        flag_received_seed = 0;
      }
      else if (msg.toString().split(':')[0] == 'size') {
        console.log('size reçu :', msg.toString().split(':')[1]);
        size = parseInt(msg.toString().split(':')[1]);
      } else {
        // const size = parseInt(msg.slice(0, 4).toString('hex'), 16);
        // Réception du contenu du fichier


        // socket_python.on('data', (data) => {
        //console.log('data reçu');
        flag_received_utilisateur = 1;
        flag_received_seed = 1;
        received += msg.length;
        chunks.push(msg);
        console.log('received :', received);
        //console.log('size :', chunks.length);
        // Si tout le fichier a été reçu et que il n'est pas en double
        if (received >= size /*&& size > 4 && received > 0 && size != buffer_mem.length*/) {
          console.log('fin de la réception de l\'image');
          // Concaténation des chunks en un seul Buffer
          const buffer = Buffer.concat(chunks);
          if (buffer_mem != buffer) {
            // Nom du fichier
            var filename_web = `images-IA/image_${Date.now()}.png`;// Date.now() : temps en millisecondes écoulées depuis le 1er janvier 1970
            var filename = `public/${filename_web}`;
            // Écriture du fichier sur le disque
            fs.writeFile(filename, buffer, (err) => {
              if (err) throw err;
              console.log(`Fichier ${filename} enregistré`);
              // Envoi du nom du fichier au client
              var msgToSend = {
                id: image_IA_nb - 1,
                data: filename_web
              }
              buffer_msgToSend[image_IA_nb - 1] = msgToSend;
              received_from_python = 1;

            });
            received = 0;
            chunks = [];
          }
          socket_python.write('Python Socket Server received data');
          buffer_mem = buffer;
          image_IA_nb++;
          GPU_disponible = 1;
          //flag_received_utilisateur = 1;
          //flag_received_seed = 1;
        }
        // });
      }
      msg = '';
    });
    //socket_python.write('Python Socket Server received data');
    socket.on('Gen_Request', (msg_by_web) => {
      console.log('msg_by_web :', msg_by_web);
      id++;
      requests[id] = socket;
      //rajouter l'utilisateur
      //console.log('utilisateur :', utilisateur);
      msg_by_web.Utilisateur = id;
      buffer_prompts[id] = msg_by_web;
      socket.emit('position_queue', id - utilisateur_read);
      if (utilisateur_read + 1 == id) {
        socket_python.write(JSON.stringify(msg_by_web));
      } else {
        console.log('gpu occupé');
        console.log('utilisateur_read :', utilisateur_read);
        console.log('id :', id);
      }
    });
  });
});
//si le serveur python est déconnecté
pythonSocketServer.on('close', () => {
  console.log('Python Socket Server disconnected');
  received_connection = 0;
});
