//Soluzione compito A
//Nota: soluzione adattata dal compito svolto da uno degli studenti che hanno
//preso 30 e lode. 


//File EsprIntera.java

package esprintera;

abstract public class EsprIntera implements Cloneable {                                                      

  /* COMPLESSITA' equals() di EsprIntera : Su oggetti di tipo
   * EsprIntera si guarda soltanto l'appartenenza dei due oggetti alla
   * stessa classe, quindi si esegue un solo confronto e la
   * complessita è 0(1)
   **/
  
  public boolean equals(Object o) {
    if(o!=null && getClass().equals(o.getClass()))
      return true;
    else return false;
  }

  public int hashCode() { //non e' usato ma in ogni caso rispettiamo il
                          //vincolo imposto da equals
    return getClass().hashCode();
  }
  
  
  public Object clone() {
    try {
      EsprIntera esp = (EsprIntera)super.clone();
      return esp;
    } catch (CloneNotSupportedException e) {
      throw new InternalError(e.toString());
    }
  }
    
}
  
  
  /*
    Parte 3   
    
    Calcolare inoltre la complessità della verifica di uguaglianza
    profonda su oggetti di tipo EsprIntera.
    
    RISPOSTA FINALE:
    
    La struttura logica dell'esercizio proposto implementa
    naturalmente un albero n-ario, quindi mettendoci nel caso
    peggiore, (cioè quando due alberi sono uguali oppure sono diversi
    ma solo per l'ultimo nodo) diventa evidente che ogni nodo deve
    essere visitato almeno una volta, quindi avendo n nodi, il costo
    di confronto di due alberi è lineare, cioè 0(n);
    
    (Nota: se le dimensioni "n_alb1" e "n_alb2" dei 2 alberi sono diverse, 
    con n_alb1<n_alb2  equals() effettuerà al piu n_alb1 controlli).
    
  */
  

  
//File Costante.java  
   
package esprintera;

public class Costante extends EsprIntera { 
  
  private int valore;
  
   	public Costante(int val){
          this.valore = val;
   	}
  
  public int valore() {
    return valore;
  }
   	
  /* COMPLESSITA' equals() di Costante:
   * Su oggetti di tipo Costante si fa un solo confronto sul campo valore 
   * (oltre all'equals() della classe padre), quindi anche qui 
   * la complessita è 0(1)
   */
   	
  public boolean equals(Object o) {
    if(super.equals(o)) {
      Costante cost = (Costante)o;
      return valore == cost.valore;
    }
    else return false;
  }
   	
  public Object clone() {
    Costante cost = (Costante)super.clone();
    return cost;
  }
  
  public int hashCode() {
    return (new Integer(valore)).hashCode();
  }
  
  public String toString() {
    return "" + valore;
  }
  
}


//File Variabile.java
   
package esprintera;

public class Variabile extends EsprIntera { 	
  private String nome;
  
  public Variabile(String nome){
    this.nome = nome;
  }
  
  public String nome() {
    return nome;
  }
  
  /* COMPLESSITA' equals() di Variabile:
   * Su oggetti di tipo Variabile si fa un solo confronto sul campo nome
   * (oltre all'equals() della classe padre), quindi anche qui 
   * la complessita è 0(1)
   */
  
  public boolean equals(Object o) {
    if(super.equals(o)) {
      Variabile var = (Variabile)o;
      return nome.equals(o.nome);
    }
    else return false;
  }
   	
  public Object clone() {
    Variabile var = (Variabile)super.clone();
    return var;
  }
  
  public int hashCode() {
    return nome.hashCode();
  }
  
  public String toString() {   
    return nome;
  }
}

//File Prodotto.java
   
package esprintera;
   
public class Prodotto extends EsprIntera {
                                            
  private EsprIntera operando1;
  private EsprIntera operando2;
   	
  public Prodotto(EsprIntera e1, EsprIntera e2){
    this.operando1 = e1;
    this.operando1 = e2;
  }
   	
  public EsprIntera operando1() {
    return operando1;
  }
  
  public EsprIntera operando2() {
    return operando2;
  }
  
  /* COMPLESSITA' equals() di Prodotto: Su oggetti di tipo Prodotto
   * per confrontare due oggetti bisogna effettuare nel caso peggiore
   * n confronti, dove n è il numero di nodi dell'albero binario che
   * si estende per (e solo per) oggetti di tipo Prodotto, quindi in
   * questo caso la complessita diventa 0(n);
   */
   	 
  public boolean equals(Object o) {
    if(super.equals(o)) {
      Prodotto pro = (Prodotto)o;
      return operando1.equals(pro.operando1) && operando2.equals(pro.operando2);
    }
    else return false;
  }
   	
  public Object clone() {
    Prodotto pro = (Prodotto)super.clone(); 
    pro.operando1 = (EsprIntera)operando1.clone();
    pro.operando2 = (EsprIntera)operando2.clone();
    return pro;
  }
   	
  public int hashCode() {
    return operando1.hashCode() + 31*operando2.hashCode();
  }
   	
  public String toString() {
    return "(" + operando1.toString() + " * " + operando2.toString() + ")";;
  }
  
}


//File Somma.java
   
package esprintera;

import java.util.*;

public class Somma extends EsprIntera {
                                         
  protected List operandi;
  
  public Somma(List oper) {   
    //Controlla che gli operandi siano almeno due e che siano
    //effettivamente EsprIntere
    if (oper.size() < 2) 
      throw new RuntimeException("espressione illegale");
    Iterator it=oper.iterator();
    operandi=new LinkedList();
    while (it.hasNext()) {
      Object e =it.next();
      if (e instanceof EsprIntera) operandi.add(e);
      else throw new RuntimeException("espressione illegale");
    }
  }

  //Era possibile anche introdurre un costruttore a zero argomenti e
  //opprotuni  metodi per aggiungere ed eliminare il numero degli operandi

  public Iterator operandi() {
    return new SommaIterator(this);
  }
   	
  /* COMPLESSITA' equals() di Somma: Su oggetti di tipo Somma per
   * confrontare due oggetti e considerando di avere n operandi
   * bisogna effettuare nel caso peggiore n confronti, dove n è il
   * numero di nodi dell'albero ennario, quindi in questo caso la
   * complessita è 0(n);
   */
  
  public boolean equals(Object o) {
    if(super.equals(o)) {
      Somma s = (Somma)o;
      Iterator it1 = operandi.iterator();
      Iterator it2 = s.operandi.iterator();
      
      while(it1.hasNext() && it2.hasNext()) //equals() e' ricorsivo
        if(!it1.next.equals(it2.next())) return false;
      
      if(it1.hasNext() || it2.hasNext()) return false;
      else return true;
      
    }
    else return false;
  }
  
  public Object clone() {
    Somma s = (Somma)super.clone(); 
    Iterator it = operandi.iterator();
    while(it.hasNext()) {
      EsprIntera esp = (EsprIntera)it.next();
      s.operandi.add(esp.clone());      //clone() e'ricorsivo
    }
    return s;
  }
  
  public int hashCode() {
    int ris = 0;
    Iterator it = operandi.iterator();
    while(it.hasNext()) {
      ris = 31*ris + it.next().hashCode(); 
    }
    return ris;
    
  }
  
  public String toString() {
    String ris = "(";
    Iterator it = operandi.iterator();
    while(it.hasNext()) {
      ris = ris + it.next.toString();  
      if(it.hasNext()) ris = ris + "+";
    }
    return ris + ")";
  }
  
}


//File SommaIterator.java

package esprintera;

import java.util.*;

class SommaIterator implements Iterator {
  private Iterator it;
  
  public SommaIterator(Somma s) {
    it = s.operandi.iterator();
  }
  
  public boolean hasNext() {
    return it.hasNext();
  }
  
  public Object next() {
    return it.next();
  }
  
  public void remove() {
    throw new UnsupportedOperationException("remove() non e' supportata"); 
  }
}


//File ServiziEsprIntere.java

package serviziesprintere;

import esprintere.*
import java.util.*;

public class ServiziEsprIntere {
  public static int valuta(EsprIntera e, Map a) {
    if(e instanceof Variabile) 
      return ((Integer)(a.get(((Variabile)e).nome()))).intValue();
    else if(e instanceof Costante)
      return ((Costante)e).valore;
    else if(e instanceof Prodotto)
      return valuta(((Prodotto)e).operando1(),a) * 
        valuta(((Prodotto)e).operando2(),a);
    else {  // (e instanceof Somma)   
      Iterator it = ((Somma)e).operandi();
      while(it.hasNext()) {
        int sum=0;
        sum = sum + valuta((EsprIntera)it.next(),a);
      }
      return sum;
    }
  }
}
