
/*
 * Proxy lato client di modello e controllo (Nim, NimPlayer, NimGame)
 *
 * Tipica sequenza di utilizzo, dove V e' di tipo NimUser,
 * C un'istanza di Socket e S di tipo NimStrategy:
 *
 *   M* = new RemoteNimGame(C);
 *     ...   ...
 *   V.setModel( M*.getModelProxy() );
 *   M*.takePartInGame(S);
 *     ...   ...
 *   String W = M*.wins();
 *     ...   ...
 *
 * Comunicazione attraverso il canale di input/output:
 *
 * <== ricezione di "RemoteSetModel", inviato dal server;
 * ==> risposta: "RemoteAck".
 *
 * <== ricezione di "RemoteUpdate", inviato dal server;
 *     notifica degli osservatori.
 *
 * <== ricezione di "RemoteSuggest", inviato dal server;
 * ==> risposta: "Nim.Move".
 *
 * <== ricezione di "RemoteOutcome", inviato dal server;
 *     sincronizzazione dei meetodi wins() / loses().
 */


package nimclient;


import nim.*;
import remote.*;

import java.util.*;
import java.net.*;
import java.io.*;


public class RemoteNimGame  extends Transceiver  implements Runnable {


  private static class NimProxy  extends Nim {
  
    private Nim nim;
  
    NimProxy( Nim nim ) {
    
      super( 0 );  // Stato interno ereditato da Nim non utilizzato
      
      this.nim = nim;
    }
    
    // Protocollo di Nim, che rimanda alla copia incapsulata
    
    public int heaps() {
    
      return nim.heaps();
    }
    
    public int matches( int heap ) {
    
      return nim.matches( heap );
    }
    
    public int matches() {
    
      return nim.matches();
    }
    
    public boolean over() {
    
      return nim.over();
    }
    
    public void remove( int heap, int matches ) {}
    
    // remove( Move move ) ereditato
    
    // Mirroring dello stato del gioco (accesso limitato al package):
    // solo in questo modo e' possibile modificare lo stato di una istanza NimProxy
    
    void mirror( Nim nim ) {
    
      this.nim = nim;
    
      setChanged();                                         // NimProxy: Observable
      notifyObservers();                                    // Notifica osservatori
    }
  
  }  // nested class NimProxy
  
  
  private NimProxy proxy = null;
  private NimStrategy strategy;
  
  private String wins = null, loses = null;
  
  
  // Costruttore:
    
  public RemoteNimGame( Socket socket ) {

    super( socket );
  }
  
  
  // Proxy del modello lato client
  
  public Nim getModelProxy() {
  
    if ( proxy == null ) {
    
      RemoteSetModel action = (RemoteSetModel) receive();   // <== server
      proxy = new NimProxy( action.nim );
      
      send( new RemoteAck() );                              // ==> server: ack
    }
    return proxy;                                           // proxy: Observable
  }
  
  
  // Gioco in corso...
  
  public void takePartInGame( NimStrategy strategy ) {
  
    this.strategy = strategy;
    
    if ( proxy != null ) {
    
      ( new Thread(this) ).start();                         // Avvio ascolto socket
    }
  }
  
  
  // Esito del gioco
  
  public synchronized String wins() {
  
    if ( (wins == null) && (loses == null) ) {
      try {
        wait();
      } catch ( InterruptedException ie ) {}
    }
    return wins;                                            // Vincitore
  }
  
  public synchronized String loses() {
  
    if ( (wins == null) && (loses == null) ) {
      try {
        wait();
      } catch ( InterruptedException ie ) {}
    }
    return loses;                                           // Perdente
  }
  
  
  // Thread di ascolto del socket
  
  public void run() {
  
    while ( !proxy.over() ) {                               // Socket event loop
    
      Object action = receive();                            // <== server
      
      if        ( action instanceof RemoteUpdate  ) {
      
        proxy.mirror( (Nim) ((RemoteUpdate) action).obs );  // Aggiornamento stato
      
      } else if ( action instanceof RemoteSuggest ) {
      
        // ( (RemoteSuggest) action ).nim  non utilizzato
        
        send( strategy.suggest(proxy) );                    // ==> server: risposta
    }}
    RemoteOutcome action = (RemoteOutcome) receive();       // <== server
      
    synchronized ( this ) {                                 // Notifica risultato
    
      wins  = action.wins;
      loses = action.loses;
      
      notifyAll();
    }
    close();                                                // Gioco concluso
  }

}  // class RemoteNimGame

