PImage backgroundImage, titleImage;
Equipe[] equipes;
Position[] positions;
Scoreboard Scoreboard;
int nombreEquipes = 42;
int timer;
PFont quicksandFont;
boolean EDITING=false;
Table positionTable;

void setup() {
  size(displayWidth, displayHeight);
  fullScreen(1);
  //randomSeed(3);
  //frameRate(60);
  colorMode(RGB, 255);
  rectMode(CENTER);
  noStroke();
  timer = millis();
  // modifier les positions des cases du plateau
  EDITING=false;
  
  // initialisation du scoreboard
  Scoreboard = new Scoreboard();
  
  positionTable = loadTable("data/positionTable.csv", "header");
  quicksandFont = loadFont("Quicksand-Bold-40.vlw");
  backgroundImage = loadImage("backgroundImage.jpg");
  titleImage = loadImage("title.png");
  
  // génération des équipes
  equipes = new Equipe[nombreEquipes];
  for (int i=0; i<equipes.length; i++) {
    Equipe e = new Equipe(i);
    equipes[i] = e;
  }
  
  // génération des emplacements
  positions = new Position[positionTable.getRowCount()];
  for (int i=0; i<positionTable.getRowCount(); i++) {
    positions[i] = new Position(positionTable.getInt(i, "id"));
    positions[i].radius = (i==0 || i==positionTable.getRowCount()-1)? 145: 60;
    positions[i].posX = positionTable.getFloat(i, "posx");
    positions[i].posY = positionTable.getFloat(i, "posy");
  }
  
  Scoreboard.applyScores();
}

// positionnement des pions sur une case, répartis en cercle si ils sont plusieurs
void drawAround(int score) {
  //println("draw around", score);
  for (int i=0; i<positionTable.getRowCount(); i++) {
    if (score>0 && score!=i) continue;
    // récupération des pions par score
    IntList pions = new IntList();
    for (TableRow row : Scoreboard.findRows(str(i+1), "score")) {
      pions.append(row.getInt("equipe")-1);
    }
    
    // positionnement des pions dont le score n'est pas nul
    if (pions.size()>0) {
      float angle = PI;
      // ce bloc est incompréhensible... mais il fait son taf de répartition des pions selon leur nombre
      for(int j=0; j<pions.size(); j++) {
        angle += TWO_PI / pions.size();
        equipes[pions.get(j)].pion.posX = pions.size() * 5.5 * cos(angle) + positions[i].posX;
        equipes[pions.get(j)].pion.posY = pions.size() * 5.5 * sin(angle) + positions[i].posY;
      }
    }
  }
}

// fonction appellée à chaque mouvement de souris
void mouseMoved() {
  for (int i = 0 ; i < equipes.length; i++) {
    Equipe e = equipes[i];
    Pion p = e.pion;
    
    // calcul de distance entre la souris et le pion
    float distPion = dist(p.posX, p.posY, mouseX, mouseY);
    // calcul de distance entre la souris et l'emplacement d'équipe
    float distEquipe = dist(e.posX, e.posY, mouseX, mouseY);
    // seuil de collision
    float threshold = p.radius/2;
    
    // si la souris est au delà du seuil de collision et que le pion est dans un état actif
    if (distPion>threshold && distEquipe>threshold && (p.hovered || p.dragged)) {
      // cloture des deux états
      p.dragStop();
      p.hoverStop();
      break;
    
    // si la souris est sous le seuil de collision du pion et qu'il n'est pas déjà dans l'état survolé
    } else if ((distPion<=threshold || distEquipe<=threshold) && !p.hovered) {
      if (distEquipe<=threshold && distPion>threshold) p.lazored = true;
      // activation de l'état survolé
      p.hoverStart();
      break;
    }
  }
  
  if (!EDITING) return;
  for (int i = 0 ; i < positions.length; i++) {
    Position p = positions[i];
    float distPosition = dist(p.posX, p.posY, mouseX, mouseY);
    float threshold = p.radius/2;
    if (distPosition>threshold && (p.hovered || p.dragged)) {
      p.dragStop();
      p.hoverStop();
      break;
    } else if (distPosition<=threshold && !p.hovered) {
      p.hoverStart();
      break;
    }
  }
}

// fonction appellée à chaque appui du clic
void mousePressed() {
  for (int i = 0 ; i < equipes.length; i++) {
    Pion p = equipes[i].pion;
    float d = dist(p.posX, p.posY, mouseX, mouseY);
    if (d<p.radius/2) {
      //println(p.id+1);
      p.dragStart();
      break;
    }
  }
  
  //if (Scoreboard.mouseHover()) { 
  //  Scoreboard.toggleDisplay();
  //}
  
  Equipe clicked = findClosestEquipeFromMouse();
  float eD = dist(clicked.posX, clicked.posY, mouseX, mouseY);
  if (eD<=clicked.radius/2) {
    // désactiver l'incrémentation si le pion était encore à son emplacement d'origine
    //if (!clicked.pion.dragged) {
      if (mouseButton == LEFT) {
        clicked.incTour();
      } else if (mouseButton == RIGHT) {
        clicked.decTour();
      }
      Scoreboard.save();
      println("Clicked on Equipe", clicked.id+1, "set tour to", clicked.tour);
    //}
  }

  
  if (!EDITING) return;
  for (int i = 0 ; i < positions.length; i++) {
    Position p = positions[i];
    float d = dist(p.posX, p.posY, mouseX, mouseY);
    if (d<p.radius/2) {
      //println(p.id+1);
      p.dragStart();
      break;
    }
  }
}

// fonction appellée à chaque déplacement appuyé (drag)
void mouseDragged() {
  for (int i = 0 ; i < equipes.length; i++) {
    Pion p = equipes[i].pion;
    if (p.dragged) {
      p.posX = mouseX+p.offset.x;
      p.posY = mouseY+p.offset.y;
      // cloture de la boucle for, pour ne pas cibler plusieurs pions si c'était possible
      break;
    }
  }
  
  if (!EDITING) return;
  for (int i = 0 ; i < positions.length; i++) {
    Position p = positions[i];
    if (p.dragged) {
      p.posX = mouseX+p.offset.x;
      p.posY = mouseY+p.offset.y;
      break;
    }
  } 
}

// fonction appellée à chaque relâchement du clic
void mouseReleased() {
  for (int i = 0 ; i < equipes.length; i++) {
    Pion p = equipes[i].pion;
    if (p.dragged) {
      p.dragStop();
      equipes[i].setScore(findClosestPositionFrom(p).getScore());
      Scoreboard.save();
    }
  }
  
  if (!EDITING) return;
  for (int i = 0 ; i < positions.length; i++) {
    Position p = positions[i];
    if (p.dragged) {
      p.dragStop();
      savePositionTable();
    }
  }
}

// fonction appellée à chaque mouvement de molette
void mouseWheel(MouseEvent event) {  
  for (int i = 0 ; i < equipes.length; i++) {
    Equipe e = equipes[i];
    Pion p = e.pion;
    
    // calcul de distance entre la souris et l'emplacement d'équipe
    float distEquipe = dist(e.posX, e.posY, mouseX, mouseY);
    // seuil de collision
    float threshold = p.radius/2;
    // nouveau rayon
    float radius = p.radius + event.getCount()*-5;

    /**
     * SI la souris est sous le seuil de collision
     * ET le nouveau rayon est acceptable
     * ET le pion n'est pas en cours d'animation
    **/
    if (distEquipe<=threshold && radius>20 && radius<120 && !p.animated) p.radius = radius;
  }
}

// écrit la position des cases dans un .csv
void savePositionTable() {
  positionTable = new Table();
  
  positionTable.addColumn("id");
  positionTable.addColumn("posx");
  positionTable.addColumn("posy");
  
  for (int i = 0 ; i < positions.length; i++) {
    Position p = positions[i];
    TableRow position = positionTable.addRow();
    
    position.setInt("id", p.id);
    position.setFloat("posx", p.posX);
    position.setFloat("posy", p.posY);
  }
  
  saveTable(positionTable, "data/positionTable.csv", "csv");
}

// renvoie la Position la plus proche du Pion fourni en paramètre
Position findClosestPositionFrom(Pion pion) {
  Position closest = positions[0];
  
  for (int i = 0 ; i < positions.length; i++) {
    Position position = positions[i];
    
    float distPion = dist(pion.posX, pion.posY, position.posX, position.posY);
    float distClosest = dist(pion.posX, pion.posY, closest.posX, closest.posY);

    if (distPion<=distClosest) closest = positions[i];
  }
  
  return closest;
}

// renvoie l'emplacement d'équipe le plus proche de la souris
Equipe findClosestEquipeFromMouse() {
  Equipe closest = equipes[0];
  
  for (int i = 0 ; i < equipes.length; i++) {
    Equipe equipe = equipes[i];
    
    float distPion = dist(mouseX, mouseY, equipe.posX, equipe.posY);
    float distClosest = dist(mouseX, mouseY, closest.posX, closest.posY);

    if (distPion<=distClosest) closest = equipes[i];
  }
  
  return closest;
}

// test de présence du fichier en paramètre
boolean fileExists(String filename) {
  File f = dataFile(filename);
  return f.isFile();
}

// LAZOR !!!
void LAZOR(PVector source, PVector target, int noiseLevel) {
  stroke(random(0, 255), random(0, 100), random(0, 100));
  strokeWeight(random(0,3));
  line(source.x, source.y, target.x, target.y);
}

void LAZOR(PVector source, PVector target) {
  LAZOR(source, target, 0);
}

// RECTANGULAR LAZOR !!!
void LAZORECT(float posX, float posY, float width, float height) {
  stroke(random(0, 255), random(0, 100), random(0, 100));
  strokeWeight(random(0,3));
  rect(posX, posY, width, height);
  
  //LAZOR(new PVector(posX, posY), new PVector(posX+width, posY));
  //LAZOR(new PVector(posX+width, posY), new PVector(posX+width, posY+height));
  //LAZOR(new PVector(posX+width, posY+height), new PVector(posX, posY+height));
  //LAZOR(new PVector(posX, posY+height), new PVector(posX, posY));
}

// boucle d'affichage appellée plusieurs fois par seconde (~60)
void draw() {
  println(frameRate);
  // afficher l'image de fond
  image(backgroundImage, 0, 0, displayWidth, displayHeight);
  
  // afficher le titre si le scoreboard est masqué
  // if (!Scoreboard.display) 
  image(titleImage, 0, 0, displayWidth, displayHeight);
  
  // afficher le scoreboard
  Scoreboard.draw();
  
  // mettre à jour les équipes
  for (int i = 0 ; i < equipes.length; i++) {
     equipes[i].update();
  }
  
  // recharger les scores depuis le fichier csv
  if (millis() > timer + 2000) {
    Scoreboard.reloadFromDisk();
    timer = millis();
  }
  
  // si on est pas en mode édition, arrêter là
  if (!EDITING) return;
  // afficher les positions à éditer
  for (int i = 0 ; i < positions.length; i++) {
   positions[i].draw();
  }
}
