package org.prevayler.implementation;
import org.prevayler.Clock;
import org.prevayler.Query;
import org.prevayler.foundation.Cool;
import org.prevayler.foundation.DeepCopier;
import org.prevayler.foundation.serialization.Serializer;
import org.prevayler.implementation.publishing.TransactionPublisher;
import org.prevayler.implementation.publishing.TransactionSubscriber;
import java.io.IOException;
import java.util.Date;

public class PrevalentSystemGuard implements TransactionSubscriber {
  private Object _prevalentSystem;
  private long _systemVersion;
  private boolean _ignoreRuntimeExceptions;
  private final Serializer _journalSerializer;
  public PrevalentSystemGuard(  Object prevalentSystem,  long systemVersion,  Serializer journalSerializer){
    _prevalentSystem=prevalentSystem;
    _systemVersion=systemVersion;
    _ignoreRuntimeExceptions=false;
    _journalSerializer=journalSerializer;
  }
  public Object prevalentSystem(){
synchronized (this) {
      if (_prevalentSystem == null) {
        throw new Error("Prevayler is no longer allowing access to the prevalent system due to an Error thrown from an earlier transaction.");
      }
      return _prevalentSystem;
    }
  }
  public void subscribeTo(  TransactionPublisher publisher) throws IOException, ClassNotFoundException {
    long initialTransaction;
synchronized (this) {
      _ignoreRuntimeExceptions=true;
      initialTransaction=_systemVersion + 1;
    }
    publisher.subscribe(this,initialTransaction);
synchronized (this) {
      _ignoreRuntimeExceptions=false;
    }
  }
  public void receive(  TransactionTimestamp transactionTimestamp){
    Capsule capsule=transactionTimestamp.capsule();
    long systemVersion=transactionTimestamp.systemVersion();
    Date executionTime=transactionTimestamp.executionTime();
synchronized (this) {
      if (_prevalentSystem == null) {
        throw new Error("Prevayler is no longer processing transactions due to an Error thrown from an earlier transaction.");
      }
      if (systemVersion != _systemVersion + 1) {
        throw new IllegalStateException("Attempted to apply transaction " + systemVersion + " when prevalent system was only at "+ _systemVersion);
      }
      _systemVersion=systemVersion;
      try {
        capsule.executeOn(_prevalentSystem,executionTime,_journalSerializer);
      }
 catch (      RuntimeException rx) {
        if (!_ignoreRuntimeExceptions)         throw rx;
      }
catch (      Error error) {
        _prevalentSystem=null;
        throw error;
      }
 finally {
        notifyAll();
      }
    }
  }
  public Object executeQuery(  Query sensitiveQuery,  Clock clock) throws Exception {
synchronized (this) {
      if (_prevalentSystem == null) {
        throw new Error("Prevayler is no longer processing queries due to an Error thrown from an earlier transaction.");
      }
synchronized (_prevalentSystem) {
        return sensitiveQuery.query(_prevalentSystem,clock.time());
      }
    }
  }
  public PrevalentSystemGuard deepCopy(  long systemVersion,  Serializer snapshotSerializer) throws IOException, ClassNotFoundException {
synchronized (this) {
      while (_systemVersion < systemVersion && _prevalentSystem != null) {
        Cool.wait(this);
      }
      if (_prevalentSystem == null) {
        throw new Error("Prevayler is no longer accepting transactions due to an Error thrown from an earlier transaction.");
      }
      if (_systemVersion > systemVersion) {
        throw new IllegalStateException("Already at " + _systemVersion + "; can't go back to "+ systemVersion);
      }
synchronized (_prevalentSystem) {
        return new PrevalentSystemGuard(DeepCopier.deepCopyParallel(_prevalentSystem,snapshotSerializer),_systemVersion,_journalSerializer);
      }
    }
  }
}
