/*
 * ChercheurInfo.java
 *
 * Created on 21 mai 2005, 16:47
 */

package examples;

import java.awt.Color;
import java.awt.Image;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import modelling.ActToSatisfaction;
import modelling.CircularArea;
import modelling.RectangularArea;
import modelling.Satisfactor;
import modelling.Selector;
import sema.Area;
import sema.Element;
import sema.World;

/**
 * nervosite " temps de décision, de se rendre compte des chses etc "
 * speed vitesse de déplacement
 *travailleur : donne le rapport de minutes que l'individu va passer au travaille
 *taillevessie : taille de la vessie typiquement 5 (cafés)
 *
 *
 *
 *ps trop travailleur il ne va pas discuter avec les autres et donc ne saura jamais par le dialogue ou est la machine à café
 * @author Moi
 */
public class ChercheurInfo extends MovingAgent3 {
    
    /** Creates a new instance of ChercheurInfo */
    
    
    public ChercheurInfo(World world,String name,Image image,Image pisseImg,Image attendImg,Image marcheImg, Image marcheImg2,Image travailleImg) {
	this(world,name,image,0f,0f,0.5f,0.5f,2f,pisseImg,attendImg,marcheImg,marcheImg2,travailleImg);
    }
    
    public ChercheurInfo(World world,String name,Image image,float bureaux,float bureauy,float travailleur,float taillevessie, float nervosite,Image pisseImg,Image attendImg,Image marcheImg, Image marcheImg2,Image travailleImg) {
	super(world,new CircularArea2(world.map,5,5,0.4f,0),0.1f,image,0.1f,selectCollision);
	this.nervosite = nervosite;
	
	this.personnalVision = world.nature.new CanSee(this);
	setDelay(nervosite/20f);
	this.speed = 1f;
	setSpeed(speed);
	setVocabulaire();
	this.travailleur = travailleur;
	this.taillevessie = taillevessie;
	this.bureaux = bureaux;
	this.bureauy = bureauy;
	this.pisseImg = pisseImg;
	this.attendImg = attendImg;
	this.marcheImg = marcheImg;
	this.marcheImg2 = marcheImg2;
	this.travailleImg = travailleImg;
	
	this.imageCourante = attendImg;
	setDrawingPriority(400);
	this.name = name;
	(new Biologie()).start();
	(new Next()).start();
    }
    String name;
    public String getName() {
	return name;
    }
    
    class Biologie extends Action {
	protected float step() {
	    cotatravaille -= 1*travailleur/2;
	    return 15;
	}
    }
    
    static Selector selectCollision = new Selector() {
	public boolean isFiting(sema.Element e) {
	    if(e.getClass() == Objet2.class) return true;
	    return false;
	}
    };
    
    class enMarchant extends Satisfactor { //satisfactor de la marche pour permettre la discution si y en a envie.
	float x,y,p;
	enMarchant(float x,float y, float p) {
	    this.x = x;
	    this.y = y;
	    this.p = p;
	}
	boolean i;
	public boolean isSatisfying() {
	    setSpeed(speed());
	    if (i) {
		imageCourante = marcheImg;
		i=false;
	    } else {
		imageCourante = marcheImg2;
		i=true;
	    }
	    discute();
	    float yy = (getArea().getY()-y);
	    float xx =(getArea().getX()-x);
	    return (Math.sqrt(yy*yy + xx*xx)<=p); //s'approche à p de sa cible.
	}
    }
    
    float travailleur;
    float cotatravaille;
    float travailleur() {
	return Math.min(travailleur-cotatravaille/2,1);
    }
    boolean acceptTravailler() {
	return (travailleur >= cotatravaille);
    }
    
    float taillevessie;
    float cotavessie;
    boolean tropCaffe() {
	return (cotavessie >= taillevessie);
    }
    
    float nervosite;
    float nervosite() {
	if (tropCaffe()) return 3*nervosite;
	return nervosite;
    }
    
    float speed;
    float speed() {
	return nervosite()*speed;
    }
    
    
    ////////////////// Cafe //////////////////////////
    Distributeur machineACafe;
    Distributeur getMachineACafe() {
	return machineACafe;
    }
    
    class commandecaffe extends Action {
	boolean ademande = false;
	protected float step() {
	    if(!ademande) {
		new dis("Café, s'il vous plait.");
		if (machineACafe.askCoffe(ChercheurInfo.this)) ademande = true;
	    }
	    return 1;
	}
    }
    Satisfactor  caffepret = new Satisfactor() { //regarde si le café est pret si oui le prend.
	public boolean isSatisfying() {
	    imageCourante = attendImg;
	    if (machineACafe.use(ChercheurInfo.this) && machineACafe.coffeReady()) {
		new dis("Slurp !");
		machineACafe.takeCoffe();
		cotavessie += 1;
		return true;
	    } else return false;
	}
    };
    
    class acheteCaffe extends ActToSatisfaction { //comande et attend que le café soit pret, puis le prend.
	acheteCaffe(){
	    super(ChercheurInfo.this,new commandecaffe(),caffepret,new Next(),new Next());
	}
    }
    
    class vaCaffer extends ActToSatisfaction { // Va acheter du caffe en marchant et donc en discutant s'il le veut sur le chemin
	vaCaffer() {
	    super(ChercheurInfo.this, new MoveTo(machineACafe.getArea().getX(),machineACafe.getArea().getY()),
		    new enMarchant(machineACafe.getArea().getX(),machineACafe.getArea().getY(),2f), new acheteCaffe(),new Next());
	}
    }
    
    Selector connaitmachinesel = new Selector() {
	public boolean isFiting(Element e){
	    if (e.getClass() == ChercheurInfo.class) {
		Distributeur d = ((ChercheurInfo) e).getMachineACafe();
		return (d!=null);
	    }
	    return false;
	}
    };
    
    Satisfactor connaitmachine = new Satisfactor() {
	public boolean isSatisfying() {
	    imageCourante = marcheImg;
	    Distributeur m = (Distributeur) watchForNearest(world.nature.getGoodKind("Machine à café"));
	    if (m!=null)  {
		machineACafe = m;
		new dis("Oh le distributeur !");
	    } else { //on ne l'a pas trouvé alors on demande dans le radius de discution.
		float vi = visionRadius;
		visionRadius = discusionRadius;
		ChercheurInfo e = (ChercheurInfo) watchForNearest(connaitmachinesel); //on regarde le plus proche chercheur qui connait la machine
		visionRadius = vi;
		if (e!=null) {
		    
		    machineACafe = (e.getMachineACafe()); // si on en a un on lui demande ou elle est.
		    new dis("Le distributeur est en au fond de la passerelle ! Merci !");
		}
	    }
	    return machineACafe != null;
	}
    };
    class chercheMachineACafe extends ActToSatisfaction {   // erre jusqu'à apprendre ou est la machine à café ou à la trouver.
	chercheMachineACafe() {
	    super(ChercheurInfo.this,new MoveToRandomGoals(2f),connaitmachine,new Next(),new Next());
	}
    }
    
    
    
    
    //////////// Travaille ////////////////
    float bureaux;
    float bureauy;
    
    class travaille extends Action { // on travaille tant que l'on a pas dépasser ses limites de travaille
	protected float step() {
	    if (acceptTravailler()) {
		imageCourante = travailleImg;
		cotatravaille += 1;
		return 30;
	    } else {
		(new Next()).start();
		return -1;
	    }
	}
    }
    
    class vaTravailler extends ActToSatisfaction { // va travailler donc va à son bureau et travaille, si en route il veu discuter il le fait.
	vaTravailler() {
	    super(ChercheurInfo.this,new MoveTo(bureaux,bureauy),new enMarchant(bureaux,bureauy,0.5f),new travaille(),new Next());
	}
    }
    
    
    
    
    
    ///////////// Urine //////////////////
    
    Element toilettes;
    float toilettesx;
    float toilettesy;
    void setToilettes(Element e) {
	toilettes = e;
	toilettesx = e.getArea().getX();
	toilettesy = e.getArea().getY();
    }
    Element getToilettes() {
	return toilettes;
    }
    
    
    Selector connaittoilettessel = new Selector() {
	public boolean isFiting(Element e){
	    if (e.getClass() == ChercheurInfo.class) {
		Element t = ((ChercheurInfo) e).getToilettes();
		return (t!=null);
	    }
	    return false;
	}
    };
    
    Satisfactor connaittoilettes = new Satisfactor() {
	public boolean isSatisfying() {
	    imageCourante = marcheImg;
	    Element t = watchForNearest(world.nature.getGoodKind("Toilettes"));
	    if (t!=null) {
		setToilettes(t); //on a trouvé tout seul la machine
		new dis("Oh les toilettes !");
	    } else { //on ne l'a pas trouvé alors on demande dans le radius de discution.
		float vi = visionRadius;
		visionRadius = discusionRadius+1;
		ChercheurInfo e = (ChercheurInfo) watchForNearest(connaittoilettessel); //on regarde le plus proche chercheur qui connait la machine
		visionRadius = vi;
		if (e!=null) {
		    setToilettes(e.getToilettes()); // si on en a un on lui demande ou elle est.
		    new dis("Ah bon les toilettes sont là bas ? Merci !");
		}
	    }
	    return toilettes != null;
	}
    };
    
    class cherchetoilettes extends ActToSatisfaction {   // erre jusqu'à apprendre ou est la machine à café ou à la trouver.
	cherchetoilettes() {
	    super(ChercheurInfo.this,new MoveToRandomGoals(2f),connaittoilettes,new Next(),new Next());
	}
    }
    
    class urine extends Action {
	protected float firstStep() {
	    imageCourante = pisseImg;
	    return 30;
	}
	protected float step() {
	    cotavessie = 0; // on vide la vessie
	    imageCourante = attendImg;
	    (new Next()).start();
	    return -1;
	}
    }
    
    class vaUriner extends ActToSatisfaction {
	vaUriner() {
	    super(ChercheurInfo.this,new MoveTo(toilettesx,toilettesy),new enMarchant(toilettesx,toilettesy,2.3f),new urine(),new Next());
	}
    }
    
    /////////// DISCUTE //////////////
    
    boolean dialogue = false;
    
    String [][] vocab;
    
    public void setVocabulaire(){
	vocab = new String[3][8];
	String[] p0 = {"Ca va?", "Viens boire un café...", "Alors ce match?", "Ah! Bonjour", "Au boulot pour changer?","Bien, merci","T'aurais un pass?","Coucou! Ca faisait longtemps!"};
	String[] p1 = {"Moi ca va, Imhotep","A une prochaine fois?","T'aurais dû venir!","Bien, merci!", "Et oui! Faut bien", "Tu manges ici?", "Je vais faire des photocop'","Comment vont les enfants?"};
	String [] p2 = {"Alors a bientôt!", "Moi aussi!","Les dictionnères tuent la foret","Bisous","Salut!","A plus", "Lol!","Ah! ah! ah!"};
	vocab[0] = p0;
	vocab[1] = p1;
	vocab[2] = p2;
    }
    
    void ditqqchose(int i) {
	try{new dis(vocab[nbphrase - 3*((int) (nbphrase/3))][i]);
	}catch(Exception e){new dis("D'accord!");}
	
    }
    
    int nbphrase = 0;
    
    class Parle extends Action {
	
	int i = (int) (Math.random()*8);
	
	protected float firstStep() {
	    imageCourante = attendImg;
	    dialogue = true;
	    nbphrase = 0;
	    setSpeed(0.5f);
	    return step();
	}
	protected float step() {
	    ditqqchose(i);
	    nbphrase ++;
	    return 2f;
	}
    }
    
    class finDiscution extends Action {
	protected float firstStep() { //quand on arete la discution on remet l'actoin courante en cour si cela échou ( pas normal) on apel next pour décider quoi faire.
	    dialogue = false;
	    engaged = false;
	    nbphrase = 0;
	    setSpeed(speed());
	    (new Next()).start();
	    return -1;
	}
    }
    
    float discusionRadius = 4;// rayon de possibilité de parler  = 2
    
    class canDiscuteSat  extends Satisfactor {	//vérifi que l'on peu toujours parler avec le monsieur
	ChercheurInfo e;
	canDiscuteSat(ChercheurInfo e) {
	    this.e=e;
	}
	public boolean isSatisfying() {
	    orientate(e.getArea().getX(), e.getArea().getY());//aller un peu vers l'interlocuteur et s'orienter vers lui
	    walk();
	    
	    return (/*personnalVision.isFiting(e) &&*/ world.nature.getDistance(ChercheurInfo.this,e) > discusionRadius || nbphrase > 2);
	}
    };
    
    class Discute extends ActToSatisfaction {
	Discute(ChercheurInfo e) {
	    super(ChercheurInfo.this,new Parle(),new canDiscuteSat(e),new finDiscution(),new finDiscution());
	}
    }
    Selector discuteselector = new Selector() {
	public boolean isFiting(Element e) {
	    return (e.getClass() == ChercheurInfo.class && e != ChercheurInfo.this);
	}
    };
    boolean engaged = false;
    void discute() {
	if (!engaged) {
	    if (Math.random()<travailleur()) return; //decide s'il va discuter
	    float vi = visionRadius;
	    visionRadius = discusionRadius; // met le radius à une limite acceptable pour dialoguer
	    ChercheurInfo e = (ChercheurInfo) watchForNearest(discuteselector);
	    visionRadius = vi; //remet le radius normal
	    
	    if (e!=null) { //il a trouvé un interlocuteur
		engaged = true;
		Object[] a = actions.toArray();
		for(int i =0;i<a.length;i++) {
		    courant = null;
		    ((Action) a[i]).stop();
		}
		
		(new Discute(e)).start(); //et discute avec le gas
	    }
	    //sinon rien.
	}
    }
    
    
    //////// PAUSE ////////
    Satisfactor pausensat = new Satisfactor() {
	public boolean isSatisfying() {
	    imageCourante = marcheImg;
	    discute();
	    return (acceptTravailler() || !tropCaffe());
	}
    };
    class faitPause extends ActToSatisfaction {
	faitPause() {
	    super(ChercheurInfo.this, new MoveToRandomGoals(2f), pausensat,new Next(),new Next());
	}
    }
    
    HashSet actions = new HashSet();
    
    protected void registerAction(Action a) {
	if(a.getClass() != Biologie.class && a.getClass() != dis.class) {
	    actions.add(a);
	}
    }
    protected void unregisterAction(Action a) {
	actions.remove(a);
    }
    
    Action courant;
    class Next extends Action {
	protected float firstStep() {
	    if (courant != null) courant.stop();
	    if (toilettes == null)  courant = new cherchetoilettes();
	    else if (tropCaffe()) courant = new vaUriner();
	    else if (acceptTravailler()) courant = new vaTravailler();
	    else if (machineACafe == null) courant = new chercheMachineACafe();
	    else if (!tropCaffe()) courant = new vaCaffer();
	    else courant = new faitPause();
	    courant.start();
	    return -1;
	}
    }
    
    Image travailleImg;
    Image marcheImg;
    Image marcheImg2;
    Image attendImg;
    Image pisseImg;
    Image imageCourante;
    public Image getImage(){
	return imageCourante;
    }
    
    
    
    
    public String getActivity(){
	if (courant != null) {
	    Class c = courant.getClass();
	    if (c == cherchetoilettes.class) return "Cherche les toilettes";
	    if (c == chercheMachineACafe.class) return "Cherche la machine à café";
	    if (c == vaUriner.class) return "Veut uriner";
	    if (c == vaTravailler.class) return "Veut travailler";
	    if (c == vaCaffer.class) return "Veut prendre un café";
	}
	return "ne sait que faire";
    }
    
    
    
    
    public String[][] getProperties() {
	String[][] ret0 = getArea().getProperties();
	String[][] ret = new String[16][2];
	String[] p1 = {"name",name};
	String[] p2 = {"Activité", getActivity()};
	String[] p3 = {"Dialogue",(dialogue) ? "oui" : "non"};
	String[] p4 = {"nb cafés dans le ventre", Float.toString(cotavessie)};
	String[] p5 = {"taille de la vessie", Float.toString(taillevessie)};
	String[] p6 = {"vessie pleine",Boolean.toString(tropCaffe())};
	
	String[] p7 = {"quota de travail", Float.toString(cotatravaille)};
	String[] p8 = {"travailleur %", Float.toString(travailleur)};
	String[] p9 = {"accepte de travailler", Boolean.toString(acceptTravailler())};
	
	String[] p10 = {"bureau x", Float.toString(bureaux)};
	String[] p11 = {"bureau y", Float.toString(bureauy)};
	
	String[] p12 = {"connait toilettes", (toilettes == null) ? "non" : "oui"};
	String[] p13 = {"toilettes x", Float.toString(toilettesx)};
	String[] p14 = {"toilettes y", Float.toString(toilettesy)};
	
	String[] p15 = {"Connait machine à café", (machineACafe == null) ? "non" : "oui"};
	
	
	String[] p16 = {"Nervosite",new Float(nervosite()).toString()};
	
	
	ret[0]=p1;ret[1]=p2;ret[2]=p3;ret[3]=p4;ret[4]=p5;ret[5]=p6;ret[6]=p7;ret[7]=p8;ret[8]=p9;ret[9]=p10;ret[10]=p11;ret[11]=p12;ret[12]=p12;ret[12]=p13;ret[13]=p14;ret[14]=p15;ret[15]=p16;
	return(union(ret0,ret)); //propriétés de l'area et de l'élément
    }
    
    
    
    public void setProperties(String property, String value) {
	try {
	    getArea().setProperties(property,value);
	    if (property.equals("name")) name = value;
	    else if (property.equals("nb cafés dans le ventre")) cotavessie = Float.parseFloat(value);
	    else if (property.equals("taille de la vessie")) taillevessie = Float.parseFloat(value);
	    
	    else if (property.equals("quota de travail")) cotatravaille = Float.parseFloat(value);
	    else if (property.equals("travailleur %")) travailleur = Float.parseFloat(value);
	    
	    else if (property.equals("bureau x")) bureaux = Float.parseFloat(value);
	    else if (property.equals("bureau y")) bureauy = Float.parseFloat(value);
	    
	} catch (Exception e) {}
    }
    
    String bullephrase;
    boolean disphrase;
    class dis extends Action {
	dis(String phrase) {
	    bullephrase = phrase;
	    disphrase = true;
	    start(2);
	}
	protected float step() {
	    disphrase = false;
	    return -1;
	}
    }
    
    public void draw() {
	getArea().draw(world.drawing);
	world.drawing.setColor(Color.RED);
	if (tropCaffe()) world.drawing.drawString("!!!!!",getArea().getX(), getArea().getY());
	world.drawing.setColor(Color.BLACK);
	
	if(disphrase) {
	    world.drawing.drawString(bullephrase, getArea().getX(), getArea().getY()-1.5f); //TODO améliore l'affichage
	}
	
	if (isMarked())
	    getArea().highlight(world.drawing,new Color(0,1,0,.3f));
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////////////
    //////////////////// PARTIE SEEING AGENT //////////////////////////////////////////////
     /*
      * Selector determinating wether the given element is hidden.
      */
    private Selector personnalVision;
    
    float visionRadius = 8;
    
    /**
     * Returns the vision radius of the element.
     */
    protected float getVisionRadius(){
	return visionRadius;
    }
    
    /**
     * Determinates the area where the element is watching.
     */
    protected Area getAreaOfVision(){
	float x = (float) (getArea().getX()+Math.cos(getArea().getAngle())*getVisionRadius()/2);
	float y = (float) (getArea().getY()+Math.sin(getArea().getAngle())*getVisionRadius()/2);
	return new RectangularArea(world.map, x, y, getVisionRadius(), 2*getVisionRadius(), getArea().getAngle());
    }
    
    protected Area getLineVision(float angle){
	float x = (float) (getArea().getX()+Math.cos(angle)*getVisionRadius()/2);
	float y = (float) (getArea().getY()+Math.sin(angle)*getVisionRadius()/2);
	return new RectangularArea(world.map, x, y, getVisionRadius(), 0, angle);//AREA.KLENGTH
    }
    
    
    
    
    /**
     * Determinates whether the given element can be seen by our agent, according to
     */
    public boolean canSee(Element element){
	return personnalVision.isFiting(element) && (world.nature.getDistance(this, element) < getVisionRadius());
    }
    
    /**
     * Returns the visible elements, with the restrictions of the vision ability.
     */
    public Vector watch(){
	Vector seen = new Vector();
	TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
	Set cles = areaOfVision.keySet();
	Iterator iterator = cles.iterator();
	while (iterator.hasNext()) {
	    Element element = (Element) areaOfVision.get(iterator.next());
	    if (canSee(element))
		seen.add(element);
	}
	return seen;
	
    }
    
    /**
     * Returns the nearest visible element
     */
    public Element watchNearest(){
	float nearest = Float.MAX_VALUE;
	Element curentNearest = null;
	TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
	Set cles = areaOfVision.keySet();
	Iterator iterator = cles.iterator();
	while (iterator.hasNext()) {
	    Element element = (Element) areaOfVision.get(iterator.next());
	    float distance = world.nature.getDistance(element, this);
	    if ((distance < nearest) && personnalVision.isFiting(element)) {
		curentNearest = element;
		nearest = (float) distance;
	    }
	}
	return curentNearest;
    }
    
    
    /**
     * Returns the nearest element responding to the criterium induced by the given Selector <br>
     * in the vision field of the agent.
     */
    public Vector watchFor(Selector sel){
	Vector seen = new Vector();
	TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
	Set cles = areaOfVision.keySet();
	Iterator iterator = cles.iterator();
	while (iterator.hasNext()) {
	    Element element = (Element) areaOfVision.get(iterator.next());
	    if (canSee(element) && sel.isFiting(element))
		seen.add(element);
	}
	return seen;
    }
    
    
    /**
     * Returns the nearest visible element fiting the given selector.
     */
    public Element watchForNearest(Selector sel){
	float nearest = Float.MAX_VALUE;
	Element curentNearest = null;
	TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
	Set cles = areaOfVision.keySet();
	Iterator iterator = cles.iterator();
	while (iterator.hasNext()) {
	    Element element = (Element) areaOfVision.get(iterator.next());
	    float distance = world.nature.getDistance(element, this);
	    if ((distance < nearest) && sel.isFiting(element) && personnalVision.isFiting(element)) {
		curentNearest = element;
		nearest = (float) distance;
	    }
	}
	return curentNearest;
    }
    
}
