package org.prevayler.foundation.network;
import java.io.IOException;
import org.prevayler.foundation.Cool;

/** 
 * The StubbronClientReceiver is an ObjectReceiver to the Client and has
 * a connection to the Socket (Provider).
 * It uses its being in the middle of the exchange to implement a recovery
 * mechanism on session failure.
 * At startup the Replication Client, sends its current session identifier
 * to the Replication Server, which if it matches a prior session, it 
 * reconnects the session. It sends a new session identifier. 
 * If the client sends a zero session identifier it assumes it is a new 
 * connection and replies with a new session identifier to use in subsequent 
 * connections.
 */
public class StubbornClientReceiverImpl extends Thread implements ObjectReceiver {
  private static int CLOSED=0;
  private static int OPEN=1;
  private static int CONNECTED=2;
  private ObjectReceiver _client;
  private ObjectSocket _socketProvider;
  private StubbornNetwork _stubbornNetwork;
  private String _ipAddress;
  private int _port;
  private volatile boolean _wantedOpen;
  private NetworkSessionId _sessionKey;
  private int _sessionState;
  public StubbornClientReceiverImpl(  StubbornNetwork stubbornNetwork,  String ipAddress,  int port,  ObjectReceiver client){
    _stubbornNetwork=stubbornNetwork;
    _ipAddress=ipAddress;
    _port=port;
    _client=client;
    _wantedOpen=true;
    _sessionState=CLOSED;
    _sessionKey=new NetworkSessionId(0,0);
    super.setName("Prevayler Stubborn Client " + port);
    super.setDaemon(true);
    super.start();
  }
  public StubbornClientReceiverImpl(  ObjectSocket socket,  Service service){
  }
  /** 
 * Wait for the connection to occur before proceeding
 */
  protected void networkRequestToReceive(  Object object) throws IOException {
    if (sessionIsConnected()) {
      _client.receive(object);
    }
 else {
      receiveSessionKey(object);
      wakeUpSleepingClient();
    }
  }
  private void receiveSessionKey(  Object sessionKey){
    _sessionKey=(NetworkSessionId)sessionKey;
  }
  private synchronized void wakeUpSleepingClient(){
    _sessionState=CONNECTED;
    notify();
  }
  private void closeForReconnect(){
    try {
      _socketProvider.close();
    }
 catch (    IOException ignore) {
    }
 finally {
      _sessionState=CLOSED;
    }
  }
  public void receive(  Object object) throws IOException {
    waitIfNotOpen();
    forwardToProvider(object);
  }
  private synchronized void waitIfNotOpen(){
    try {
      if (!sessionIsConnected()) {
        wait();
      }
    }
 catch (    InterruptedException unEx) {
    }
  }
  private void forwardToProvider(  Object object){
    try {
      _socketProvider.writeObject(object);
    }
 catch (    IOException unExpected) {
      shutdown();
      waitToResend(object);
    }
  }
  private synchronized void waitToResend(  Object object){
    try {
      wait();
      forwardToProvider(object);
    }
 catch (    InterruptedException uhOh) {
    }
  }
  public void close() throws IOException {
    _wantedOpen=false;
    shutdown();
  }
  private void shutdown(){
    if (sessionIsOpen()) {
      closeForReconnect();
    }
  }
  private boolean sessionIsOpen(){
    return (_sessionState > CLOSED);
  }
  private boolean sessionIsConnected(){
    return (_sessionState == CONNECTED);
  }
  public void run(){
    while (_wantedOpen)     try {
      _socketProvider=_stubbornNetwork.newInstance(_ipAddress,_port);
      sendSessionKey();
      startReading();
    }
 catch (    IOException iox) {
      closeForReconnect();
      Cool.sleep(1000);
    }
  }
  private void sendSessionKey() throws IOException {
    _socketProvider.writeObject(_sessionKey);
    _sessionState=OPEN;
  }
  private void startReading() throws IOException {
    while (_wantedOpen) {
      try {
        Object o=_socketProvider.readObject();
        networkRequestToReceive(o);
      }
 catch (      ClassNotFoundException returnIt) {
        _socketProvider.writeObject(returnIt);
      }
    }
  }
}
