/*
 Copyright (c) 2007, Distributed Computing Group (DCG)
                    ETH Zurich
                    Switzerland
                    dcg.ethz.ch

 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

 - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.

 - Neither the name 'Sinalgo' nor the names of its contributors may be
   used to endorse or promote products derived from this software
   without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package projects.defaultProject.nodes.nodeImplementations;


import java.awt.Color;
import java.awt.Graphics;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;

import projects.defaultProject.nodes.messages.Container;
import projects.defaultProject.nodes.messages.Msg;
import projects.defaultProject.nodes.timers.*;
import sinalgo.configuration.Configuration;
import sinalgo.configuration.CorruptConfigurationEntryException;
import sinalgo.configuration.WrongConfigurationException;
import sinalgo.gui.transformation.PositionTransformation;
import sinalgo.nodes.Node;
import sinalgo.nodes.edges.Edge;
import sinalgo.nodes.messages.Inbox;
import sinalgo.nodes.messages.Message;
import sinalgo.tools.Tools;

public class BasicNode extends Node {
	protected boolean crashed=false;
	protected int identite;
	protected Random r= new Random();
	private boolean synchrone=false;
	private double delai;
	
	public boolean Crashed() {
		return this.crashed;
	}
	
	// getter pour d'identité du processus
	
	public int Id() {
		return this.identite;
	}
	
	public int nextInt(int b) {
		return r.nextInt(b);
	}
	
	// La méthode nbProcesses() retourne le nombre total de processus.
	
	public static int nbProcesses() {
		return Tools.getNodeList().size();
	}
	
	// La méthode degree() retourne le nombre de voisins du processus.
	
	public int degree() {
		return this.outgoingConnections.size();
	}
	
	// la méthode degreeMax() retourne le degree du réseau
	
	public static int degreeMax() {
		int max=0;
		for(Node n : Tools.getNodeList()) {
			int d=((BasicNode) n).degree();
			max=max<d?d:max;
		}
		return max;
	}
	
    // La méthode getIndex(x) retourne le numéro de canal correspondant au voisin x (une reférence).
    // En cas d'erreur, 0 est retourné : pas d'effet de bord avec les tableaux !

	public int getIndex(Node x)
	{
		if(x==null) return 0;
		int j=1;
		for(Edge e : this.outgoingConnections) {
			if(e.endNode.ID == x.ID) return j;
			j++;
		}
		return 0;
	}
	
	// La méthode getNeighbor(indice) retourne la référence (adresse) du voisin
	// correspondant au canal de numéro "indice".
    // Si l'indice n'existe pas, on retourne la référence du noeud (processus).
	
	public Node getNeighbor(int indice){
		 if (indice > this.degree() || indice < 1)
	          return this;
		 
			Iterator<Edge> iter=this.outgoingConnections.iterator();

			for(int j=1;j<indice;j++)
				iter.next();
			return iter.next().endNode;
	}
	
	// La méthode broadcast(m) envoie un message identique m à tous les voisins.
	
	public void broadcast(Msg m) {
		super.broadcast(new Container(this,m));
	}	
	
	// La méthode send(m,i) envoie un message au voisin relié au canal de numéro i.
	
	public void send(Msg m, int i) {
		send(new Container(this,m), getNeighbor(i));
	}
	
	// La méthode decide() correspond à l'évènement de décision.
	// ATTENTION : cela stoppe la simulation.
	
	public void affiche(String s) {
		Tools.appendToOutput(s);
	}
	
	public void decide(String s) {
		Tools.appendToOutput(s+"\n");
		Tools.stopSimulation();
	}
	
	public void decide() {
		Tools.appendToOutput("Décision\n");
		Tools.stopSimulation();
	}
	
	public static void stop(String s) {
		Tools.appendToOutput(s+"\n");
		Tools.stopSimulation();
	}
	
	public static void clearConsole() {
		Tools.clearOutput();
	}
	
	// La méthode from(m) retourne le numéro de canal par lequel est arrivé le message m.
	
	public int from(Msg m) {
		return this.getIndex(m.sender);
	}
	
	// La méthode initiator() retourne vrai si et seulement si le processus est initiateur.
	// Par défaut tout processus est initiateur.
	
	public boolean initiator() {
		return true;
	}
	
	// La méthode initialization() doit contenir l'initialisation de toutes les variables du processus.
	
	public void initialization() {}
	
	// La méthode spontaneously() doit contenir le code de démarrage de l'algorithme,
	// c'est-à-dire le code exécuté en premier par tout initiateur.
	
	public void spontaneously() {}

	// La méthode init() est exécutée par chaque processus au démarrage de la simulation (temps 0)
    // ATTENTION : lorsque la méthode init() est appelée, 
    // les canaux de communications n'existent pas encore.
    // Il faut attendre une unité de temps, 
    // avant que les connections soient réalisées.
    // Nous utilisons donc un minuteur (initTimer) pour déclencher 
    // la véritable initialisation (méthode start()) après une unité de temps.
	
	public void init() {
		try {	
		    this.synchrone=Configuration.getBooleanParameter("Sync");
		    try {
				delai = Configuration.getDoubleParameter("MessageTransmission/ConstantTime")+2.0;
			} catch (CorruptConfigurationEntryException e) {
				delai=12;
			}
		} catch (CorruptConfigurationEntryException e) {
			this.synchrone=false;
		}
		(new initTimer()).startRelative(1, this);	
	}

	
	public void regularly() {
		
	}
	
	public void regularly_container() {
		if(!this.Crashed()) {
				regularly();
				if(this.synchrone) 
					(new waitTimer()).startRelative(delai, this);	
				else
					(new waitTimer()).startRelative(5+r.nextInt(6), this);
		}
	}
	// La méthode start() correspond au début réel de l'exécution,
	// c'est-à-dire l'initialisation des variables suivie du démarrage de l'algorithme
	
	public void start(){
		// on crée tout d'abord l'identité du processus
		
		this.identite=this.ID+r.nextInt(nbProcesses())*nbProcesses();
		
		if(!this.Crashed()) {
			initialization();
			if(this.initiator()) {
				spontaneously();
			}
		}
		//regularly_container();
	}	

    // La méthode handleMessages(Inbox inbox) permet de traiter les messages reçus.
	// Les messages sont stockés dans une structure "boite aux lettres".
	// Cette méthode est appelée régulièrement même si aucun message n'a été reçu. 
	// C'est une méthode à usage interne uniquement.
	// Pour pouvoir retrouver le numéro de canal entrant, 
	// on a été contraint d'encapsuler le message réel dans un contenaire 
	// contenant aussi la référence de l'émetteur (n.b., dans le moteur, il y a un problème de copie à un moment donné).
	// Les messages sont désencapsulés puis stockés dans une file
	// (la réference de l'émetteur de chaque message est cachée dans le type Msg, 
	// cela permettra de retrouver le numéro de canal entrant en temps utile).
	// La file est ensuite passée en paramètre de la méthode receipt, 
	// la méthode "utilisateur" pour le traitement des messages reçus.
	
	public void handleMessages(Inbox inbox) {
		LinkedList<Msg> new_inbox= new LinkedList<Msg>();
		Container c;
		for(Message m : inbox) {
			c=(Container) m;
			c.m.sender=c.sender;
			if(!((BasicNode) c.sender).Crashed() || this.nextInt(2)==0)
				new_inbox.push(c.m);
		}
		if(!Crashed() && !new_inbox.isEmpty()) this.receipt(new_inbox);
		
		if(Tools.isSimulationInAsynchroneMode()) Tools.getCustomGlobal().hasTerminated();
	}
	
	// La méthode receipt(LinkedList<Msg> inbox) est la méthode à utiliser pour traiter les messages reçus.
	// Les messages sont stockés dans la file inbox.
	// ATTENTION : cette méthode est appelée régulièrement même si aucun message n'a été reçu. 
	
	public void receipt(LinkedList<Msg> inbox) {}	
	
	// La méthode draw(g,pt,highlight) affiche la représentation du processus en mode graphique.
	
	 public void draw(Graphics g, PositionTransformation pt, boolean highlight){
	 if(!this.Crashed())
		this.setColor(Color.yellow);
	 else
		this.setColor(Color.black);		 
	 String text = ""+this.ID;
	 super.drawNodeAsDiskWithText(g, pt, highlight, text, 20, Color.black);
	 }	

	 // La méthode toString() affiche dans la console les informations de bases sur le processus.
	 
	public String toString() {
		String s = "Node(" + this.Id() + ") [";
		Iterator<Edge> edgeIter = this.outgoingConnections.iterator();
		while(edgeIter.hasNext()){
			Edge e = edgeIter.next();
			BasicNode n = (BasicNode) e.endNode;
			s+=n.Id()+" ";
		}
		return s + "]";
	}

	// Ci-dessous : deux méthodes spéciales pour les topologies en anneaux orientés.
	
	// La méthode Right() retourne la référence du successeur dans l'anneau.
	
	public Node Right(){
		for(Edge e : this.outgoingConnections)
			if(e.endNode.ID==(this.ID%Tools.getNodeList().size())+1)
				return e.endNode;
		return null;
	}
	
	// La méthode sendSuccessor(m) envoie un message m au successeur dans l'anneau.
	
	public void sendSuccessor(Msg m) {
		send(new Container(this,m), Right());
	}
	
	// Gestion de la dynamicité :
	// en cas de changement topologique,
	// si le changement affecte le voisinage du processus, alors 
	// la méthode neighborhoodChange() est appelée.
	
	public void neighborhoodChange() {}
	
	// Les trois méthodes ci-dessous ne sont pas utiles.
	
	public void preStep() {}
	public void postStep() {
	}
	public void checkRequirements() throws WrongConfigurationException {}

}
