import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.net.*;
import Cercle;

/** Applet de démonstration des threads et d'animation en double
    buffer. Il reste encore à corriger le calcul de la zone
    d'évolution des cercles en fonction des bordures de l'applet
    (insets) */
    
public class AnimCircle extends Applet implements ActionListener {
  MyCanvas c;
  Label message = new Label("Zone de messages");
  Panel panelGauche;
  Scrollbar sb1, sb2;
  static boolean isApplication = false;
    
  public void init() {

    // Création de l'interface graphique
    setLayout(new BorderLayout());
        
    // Fenêtre d'animation au centre
    c = new MyCanvas();
    add("Center", c);
	
    // Creation de cercles animés dans le canvas au milieu
    c.ajouteObjetDessinable(new Cercle(100, 100, 10));
    c.ajouteObjetDessinable(new Cercle(100, 100, 20));
    c.ajouteObjetDessinable(new Cercle(100, 100, 30));

    // Fenêtre avec les boutons de contrôle à gauche
    ControlsBas controlsBas;
    add("South", controlsBas = new ControlsBas(this));
	    
    //Fenêtre avec les boutons de contrôle à droite
    ControlsDroite controlsDroite;
    add("East", controlsDroite = new ControlsDroite(this));
	    
    // Zone de message en haut
    add("North", message);
	    

    // Un panel à gauche avec deux Scrollbar. Le panel est une grille de 1 ligne et n colonnes
    panelGauche = new Panel();
    panelGauche.setLayout(new GridLayout(1, 0));
	    
    // Scrollbar à gauche pour règler la vitesse d'animation du canvas c
    sb1 = new Scrollbar(Scrollbar.VERTICAL);
    sb1.setMaximum(1000);
    sb1.setMinimum(5);
    // Valeur de déplacement de l'onglet lorsqu'on clique au-dessus ou au-dessous, dans la scrollbar
    sb1.setBlockIncrement(20);
    // Valeur de déplacement de l'onglet lorsqu'on clique sur les flèches en haut ou en bas
    sb1.setUnitIncrement(10);
    // Position de départ de l'onglet
    sb1.setValue(c.getTempsEntreFrames());
	
    // Pour gérer les événements, on utilise une classe interne anonyme
    sb1.addAdjustmentListener(
			      new AdjustmentListener() {
      public  void adjustmentValueChanged(AdjustmentEvent e) {
	sb1.setValue(e.getValue());
	c.setTempsEntreFrames(e.getValue());
	message.setText("Temps entre frames = " + c.getTempsEntreFrames() + " millisecondes");
      }
    }
			      );
    panelGauche.add(sb1);
	    
    // Autre scrollbar pour règler la vitesse de déplacement des objets animés
    sb2 = new Scrollbar(Scrollbar.VERTICAL);
    sb2.setMaximum(100);
    sb2.setMinimum(0);
    /* Valeur de déplacement de l'onglet lorsqu'on clique au-dessus ou
       au-dessous, dans la scrollbar */
    sb2.setBlockIncrement(10);
    /* Valeur de déplacement de l'onglet lorsqu'on clique sur les
       flèches en haut ou en bas */
    sb2.setUnitIncrement(5);
    // Position de départ de l'onglet
    sb2.setValue(c.getTempsEntreFrames());
	    
    /* Pour gérer les événements, on utilise une classe interne
    anonyme.  Etant donné que nous avons deux scrollbars au traitement
    des événements similaires, il aurait mieux valu utiliser une seule
    classe non anonyme, voir la classe MyCanvas pour un exemple de
    classe interne non anonyme */
    sb2.addAdjustmentListener(
			      new AdjustmentListener() {
      public  void adjustmentValueChanged(AdjustmentEvent e) {
	sb2.setValue(e.getValue());
	ObjetGraphique.setTempsEntreDeplacements(e.getValue());
	message.setText("Temps entre chaque déplacement = " + ObjetGraphique.getTempsEntreDeplacements() + 
			" millisecondes");
      }
    }
			      );
    panelGauche.add(sb2);

    add("West", panelGauche);
	    
    message.setText("Nb objets : " + ObjetGraphique.getNbObjetsCrees());
  }

  public void start(){
        
    // On réveille le thread de la fenêtre de dessin et des cercles...
    c.resume();
    ObjetGraphique.resume();
  }

  public void stop() {
    // On suspend tous les threads : celui de la fenêtre graphique et
    // les Objets graphiques
    c.suspend();
    ObjetGraphique.suspend();
  }

  /** Appelé lors d'un click sur un des boutons de contrôle */
  public void actionPerformed(ActionEvent evt) {
    String arg = evt.getActionCommand();

    if(arg.equals("Suspendre")) {
      c.suspend();
      ObjetGraphique.suspend();
    } else if(arg.equals("Reprendre")) {
      c.resume();
      ObjetGraphique.resume();
    } else if(arg.equals("Ajoute cercle")) {
      c.ajouteObjetDessinable(new Cercle(100, 100, (int) (10 + Math.rint(Math.random() * 20))));
      message.setText((String) ("Nb objets : " + ObjetGraphique.getNbObjetsCrees()));
    } else if(arg.equals("Ajoute image")) {

      if(isApplication) {
	c.ajouteObjetDessinable(new ImageAnimee(100, 100, "toto.gif"));
      }
      else {
	try {
	  c.ajouteObjetDessinable(new ImageAnimee(100, 100, 
						  new URL(getDocumentBase(), 
							  "toto.gif")));
	} catch (MalformedURLException e) {
	  System.out.println("Erreur dans le chargement de l'image, mauvais URL");
	  e.printStackTrace();
	}
      }
      message.setText("Nb objets : " + ObjetGraphique.getNbObjetsCrees());
    } else if(arg.equals("Ajoute rectangle")) {
      c.ajouteObjetDessinable(new Rectangle(100, 100, 
					    (int) (10 + Math.rint(Math.random() * 20)),
					    (int) (10 + Math.rint(Math.random() * 20))));
      message.setText("Nb objets : " + ObjetGraphique.getNbObjetsCrees());
    } else if(arg.equals("Change couleur")) {
      c.changeCouleur(Color.green);
    } else if (arg.equals("Quitter")) {
      System.exit(0);
    }
  }

  // Comme ça, cette applet pourra aussi être utilisée comme une application
  public static void main(String args[]) {
    Frame f = new Frame("AnimCircle");
    AnimCircle	anim = new AnimCircle();

    isApplication = true;

    anim.init();
    anim.start();

    f.add("Center", anim);
    f.setSize(400, 400);
    f.show();
  }
	
}

class ControlsBas extends Panel {
  ActionListener ecouteur;
    
  public ControlsBas(ActionListener ecouteur) {

    // bouton pour suspendre l'animation des objets animés
    Button b1 = new Button("Suspendre");
    add(b1);
    b1.addActionListener(ecouteur);

    // bouton pour reprendre l'animation des objets animés
    Button b2 = new Button("Reprendre");
    add(b2);
    b2.addActionListener(ecouteur);
        
    // bouton pour quitter l'application
    Button b4 = new Button("Quitter");
    b4.setEnabled(false);
    add(b4);
    b4.addActionListener(ecouteur);
  }
    
}


class ControlsDroite extends Panel {
    
  public ControlsDroite(ActionListener ecouteur) {
    // 1 colonne, n lignes...
    setLayout(new GridLayout(0, 1));
        
    // bouton pour ajouter un cercle
    Button b1 = new Button("Ajoute cercle");
    add(b1);
    b1.addActionListener(ecouteur);

    // bouton pour ajouter un rectangle
    Button b2 = new Button("Ajoute rectangle");
    add(b2);
    b2.addActionListener(ecouteur);

    // bouton pour ajouter une image
    Button b3 = new Button("Ajoute image");
    add(b3);
    b3.addActionListener(ecouteur);
        
    // bouton pour changer la couleur des objets animés
    Button b4 = new Button("Change couleur");
    add(b4);
    b4.addActionListener(ecouteur);
  }
    
}

/** Cette classe fournit un canvas affichant des objets graphiques
    Dessinable à intervalles réguliers */
class MyCanvas extends Canvas implements Runnable {
  Thread thread;
  Vector listeObjetsDessinables = new Vector();
  // Pour le double buffer
  Image offScrImage;
  Graphics offScrGC;
  int largeur, hauteur;
  // temps en millisecondes entre chaque frame d'animation
  int tempsEntreFrames = 10; 
   
  public MyCanvas() {

    // Classe anonyme interne pour gérer l'événement resize()
    ComponentListener resizeCallback = new ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
	// Définition de la zone d'évolution des cercles.
	largeur = getSize().width;
	hauteur = getSize().height;
                
	ObjetGraphique.setZoneEvolution(largeur, hauteur);

	// Le double buffer = une image mémoire de la taille de la 
	// zone d'évolution
	// des cercles
	offScrImage = createImage(largeur, hauteur);
                                          
	// Récupération d'un contexte graphique pour dessiner dans le 
	// double buffer.
	offScrGC = offScrImage.getGraphics();
	offScrGC.setColor(Color.red);

	System.out.println("Canvas Drawing1 retaillée : width = " + largeur + 
			   "height = " + hauteur);
      }
    }; // Fin de la classe anonyme

    // On indique qu'en cas de retaillage du canvas, c'est l'objet 
    // resizeCallback
    // qui va traiter l'événement
    addComponentListener(resizeCallback);
	
    // Lancement d'un thread
    thread = new Thread(this);
    thread.start();

        
  }

  /** Stoppe l'animation */
  public void resume() {
    thread.resume();
  }
    
  /** reprend lùanimation */
  public void suspend() {
    thread.suspend();
  }
    
  /** Retourne le temps entre chaque frame d'animation */
  public int getTempsEntreFrames() {
    return tempsEntreFrames;
  }
    
  /** Permet de régler le temps écoulé, en millisecondes, entre chaque 
      frame d'animation */
  public void setTempsEntreFrames(int temps) {
    tempsEntreFrames = temps;
  }

  /** Tache de fond executee par le Thread. Effectue l'animation des
      cercles en invoquant repaint(), ce qui provoque l'execution de
      paint(). Notez que run() doit impérativement contenir une
      boucle afin de ne pas rendre la main tout de suite.  */
  public void run() {

    while(true) {
      // On demande à redessiner les cercles
      repaint();

      // On "dort" tempsEntreFrame millisecondes
      try {
	thread.sleep(tempsEntreFrames);
      } catch(InterruptedException e) {
	System.out.println("Erreur dans le sleep(tempsEntreFrames);");
	e.printStackTrace();
      }
    }        
  }


  /** Ajoute à la liste des objets affiché à chaque frame
      d'animation un objet de plus */
  public void ajouteObjetDessinable(Dessinable objet) {
    listeObjetsDessinables.addElement(objet);
  }

  /** On surcharge update() car on fait du double buffer. On ne veut
      pas effacer à la fois la fenêtre graphique de l'applet, ce que
      fait par défaut update(), et effacer en plus le double buffer
      dans paint() */
  public void update(Graphics g) {
    paint(g);
  }
    
  /** Modifie la couleur du tracé des objets fil de fer */
  public void changeCouleur(Color c) {
    offScrGC.setColor(c);
  }
    
  /** La fonction qui effectue l'affichage des objets graphiques que
      doit gérer cette classe */
  public void paint(Graphics gc) {
    // appelé automatiquement par l'AWT, ou sur demande par repaint()
    Enumeration liste = listeObjetsDessinables.elements();
    Dessinable objetDessinable;

    // On efface le double buffer
    offScrGC.clearRect(0, 0, largeur, hauteur);
        
    // On affiche les objets animés
    while(liste.hasMoreElements()) {
      objetDessinable = (Dessinable) liste.nextElement();

            
      // On dessine un cercle dans le double buffer
      objetDessinable.dessineToi(offScrGC);
    }
        
    // On recopie le double buffer dans le canvas
    gc.drawImage(offScrImage, 0, 0, this);
  }
   
}

