package jist.runtime;

import java.io.IOException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;

public class RealtimeController extends Controller {

  public static class Factory extends Controller.Factory {
    public Controller createController() throws RemoteException {
      try {
        return new RealtimeController();
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }

  private volatile boolean eventsPending = false;

  private List pendingEvents;

  private volatile boolean sleeping = false;

  private volatile long realTimeStart;

  private boolean starting = true;

  protected RealtimeController() throws IOException {
    super();
    pendingEvents = new ArrayList();
  }

  /*
   * (non-Javadoc)
   * @see jist.runtime.Controller#registerEntity(jist.runtime.Entity)
   */
  public synchronized EntityRef registerEntity(Entity entity)
  {
    EntityRef ref = super.registerEntity(entity);

    // if cross-thread, then use the ThreadedEntityRef for marshalling
    if (null != thread && thread.isAlive()
        && Thread.currentThread() != thread)
      ref = new ThreadedEntityRef(ref);

    return ref;
  }

  /**
   * TODO time??
   */
  public void addPendingEvent(Method meth, EntityRef ref, Object[] params) {
    synchronized (this) {
      pendingEvents.add(new Event(0, meth, ref, params));
      this.eventsPending = true;
      if (sleeping)
//        getThread().interrupt();
        this.notify();
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.runtime.Controller#processEvent()
   */
  protected void processEvent() {
    super.processEvent();
    checkPendingEvents();
  }

  private void checkPendingEvents() {
    if (!eventsPending)
      return;

    synchronized (this) {
      while (!pendingEvents.isEmpty()) {
        Event ev = (Event) pendingEvents.remove(0);
        ev.time = getRealTime();
        addEvent(ev);
      }
      eventsPending = false;
    }
  }

  /*
   * (non-Javadoc)
   * @see jist.runtime.Controller#eventLoop()
   */
  protected long eventLoop()
  {
    long numEvents = 0;
    try
    {
      while(events.size()>0)
      {
        Event next;
        long time;
        do {
          next = events.peekFirst();
          if (starting)
            realTimeStart = System.nanoTime();
          if (next.time >= 100)
            starting = false;
          time = getRealTime();

          int millis = (int)((next.time-time)/1000000);
          if (millis > 0) {
            synchronized (this) {
              sleeping = true;
              try {
                wait(millis);
              } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
              }
              checkPendingEvents();
              sleeping = false;
            }
//            try {
//              this.sleeping = true;
//              Thread.sleep(millis);notify()
//              this.sleeping  = false;
//            } catch (InterruptedException e) {
//              Thread.interrupted();
//              checkPendingEvents();
//              continue;
//            }
          }
          do {
            time = getRealTime();
          } while (next.time > time);
        } while (next.time > time);

        currentEvent = events.removeFirst();
        currentSimulationTime = currentEvent.time;
        processEvent();
        numEvents++;
        disposeEvent(currentEvent);
      }
    }
    catch(JistException.JistSimulationEndException e) { }
    return numEvents;
  }

  private long getRealTime() {
    if (starting)
      return 0;
    return System.nanoTime() - realTimeStart;
  }

  public static long getRealTimeStatic() {
    return ((RealtimeController)getActiveController()).getRealTime();
  }

  /**
   * Create proxy entity with given interface.
   *
   * @param proxyTarget target object of proxy entity
   * @param proxyInterface public interface of proxy entity
   * @return proxy entity object
   */
  public static Object proxy(Object proxyTarget, Class proxyInterface) {
    return ProxyEntity.create(proxyTarget, new Class[] {proxyInterface});
  }

  /**
   * Quit the controller due to exception.
   *
   * @param string
   * @param e
   */
  public static void stop(String string, IOException e) {
    log.error("Terminating controller due to IO exception", e);

    RealtimeController controller =
      ((RealtimeController)getActiveController());

    Event ev = new Event(controller.getRealTime() + 1,
        JistException.JistSimulationEndException.method_end,
        controller.getStaticEntityRef(), null);
    controller.addEvent(ev);
  }

}
