package org.prevayler.implementation.replication;
import org.prevayler.Clock;
import org.prevayler.foundation.network.OldNetwork;
import org.prevayler.foundation.network.ObjectSocket;
import org.prevayler.implementation.Capsule;
import org.prevayler.implementation.TransactionTimestamp;
import org.prevayler.implementation.clock.BrokenClock;
import org.prevayler.implementation.publishing.TransactionPublisher;
import org.prevayler.implementation.publishing.TransactionSubscriber;
import java.io.IOException;
import java.util.Date;

/** 
 * Reserved for future implementation.
 */
public class ClientPublisher implements TransactionPublisher {
  private final BrokenClock _clock=new BrokenClock();
  private TransactionSubscriber _subscriber;
  private final Object _upToDateMonitor=new Object();
  private Capsule _myCapsule;
  private final Object _myCapsuleMonitor=new Object();
  private RuntimeException _myTransactionRuntimeException;
  private Error _myTransactionError;
  private final ObjectSocket _server;
  public ClientPublisher(  OldNetwork network,  String serverIpAddress,  int serverPort) throws IOException {
    System.out.println("The replication logic is still under development.");
    _server=network.openSocket(serverIpAddress,serverPort);
    startListening();
  }
  private void startListening(){
    Thread listener=new Thread(){
      public void run(){
        try {
          while (true)           receiveTransactionFromServer();
        }
 catch (        Exception ex) {
          ex.printStackTrace();
        }
      }
    }
;
    listener.setDaemon(true);
    listener.start();
  }
  public synchronized void subscribe(  TransactionSubscriber subscriber,  long initialTransaction) throws IOException, ClassNotFoundException {
    if (_subscriber != null)     throw new UnsupportedOperationException("The current implementation can only support one subscriber. Future implementations will support more.");
    _subscriber=subscriber;
synchronized (_upToDateMonitor) {
      _server.writeObject(new Long(initialTransaction));
      wait(_upToDateMonitor);
    }
  }
  public void cancelSubscription(  TransactionSubscriber subscriber){
    throw new UnsupportedOperationException("Removing subscribers is not yet supported by the current implementation.");
  }
  public synchronized void publish(  Capsule capsule){
    if (_subscriber == null)     throw new IllegalStateException("To publish a transaction, this ClientPublisher needs a registered subscriber.");
synchronized (_myCapsuleMonitor) {
      _myCapsule=capsule;
      try {
        _server.writeObject(capsule);
      }
 catch (      IOException iox) {
        iox.printStackTrace();
        while (true)         Thread.yield();
      }
      wait(_myCapsuleMonitor);
      throwEventualErrors();
    }
  }
  private void throwEventualErrors() throws RuntimeException, Error {
    try {
      if (_myTransactionRuntimeException != null)       throw _myTransactionRuntimeException;
      if (_myTransactionError != null)       throw _myTransactionError;
    }
  finally {
      _myTransactionRuntimeException=null;
      _myTransactionError=null;
    }
  }
  private void receiveTransactionFromServer() throws IOException, ClassNotFoundException {
    Object transactionCandidate=_server.readObject();
    if (transactionCandidate.equals(ServerConnection.SUBSCRIBER_UP_TO_DATE)) {
synchronized (_upToDateMonitor) {
        _upToDateMonitor.notify();
      }
      return;
    }
    if (transactionCandidate instanceof Date) {
      Date clockTick=(Date)transactionCandidate;
      _clock.advanceTo(clockTick);
      return;
    }
    if (transactionCandidate instanceof RuntimeException) {
      _myTransactionRuntimeException=(RuntimeException)transactionCandidate;
      notifyMyTransactionMonitor();
      return;
    }
    if (transactionCandidate instanceof Error) {
      _myTransactionError=(Error)transactionCandidate;
      notifyMyTransactionMonitor();
      return;
    }
    TransactionTimestamp transactionTimestamp=(TransactionTimestamp)transactionCandidate;
    Date timestamp=transactionTimestamp.executionTime();
    long systemVersion=transactionTimestamp.systemVersion();
    _clock.advanceTo(timestamp);
    if (transactionTimestamp.capsule() == null) {
      _subscriber.receive(new TransactionTimestamp(_myCapsule,systemVersion,timestamp));
      notifyMyTransactionMonitor();
      return;
    }
    _subscriber.receive(new TransactionTimestamp(transactionTimestamp.capsule(),systemVersion,timestamp));
  }
  private static void wait(  Object monitor){
    try {
      monitor.wait();
    }
 catch (    InterruptedException ix) {
      throw new RuntimeException("Unexpected InterruptedException.");
    }
  }
  private void notifyMyTransactionMonitor(){
synchronized (_myCapsuleMonitor) {
      _myCapsuleMonitor.notify();
    }
  }
  public Clock clock(){
    return _clock;
  }
  public void close() throws IOException {
    _server.close();
  }
}
