package org.testory;

import static org.testory.common.Checks.checkArgument;
import static org.testory.common.Checks.checkNotNull;

import java.util.ArrayList;
import java.util.List;

import org.testory.common.Nullable;
import org.testory.proxy.Handler;
import org.testory.proxy.Invocation;
import org.testory.util.Effect;
import org.testory.util.any.Any;

class History {
  /** youngest at begin */
  private final ThreadLocal<List<Object>> threadLocalEvents = new ThreadLocal<List<Object>>() {
    protected List<Object> initialValue() {
      return new ArrayList<Object>();
    }
  };

  public History() {}

  private List<Object> getEvents() {
    return new ArrayList<Object>(threadLocalEvents.get());
  }

  private void setEvents(List<Object> events) {
    threadLocalEvents.set(new ArrayList<Object>(events));
  }

  private void addEvent(Object event) {
    threadLocalEvents.get().add(0, event);
  }

  private static class Purge {}

  public void purge() {
    List<Object> events = getEvents();
    for (int i = 0; i < events.size(); i++) {
      if (events.get(i) instanceof Purge) {
        setEvents(events.subList(0, i));
        break;
      }
    }
    addEvent(new Purge());
  }

  public void purgeNow() {
    setEvents(new ArrayList<Object>());
  }

  public void logWhen(Effect effect) {
    checkNotNull(effect);
    addEvent(effect);
  }

  public boolean hasLastWhenEffect() {
    return tryGetLastWhenEffect() != null;
  }

  public Effect getLastWhenEffect() {
    Effect effect = tryGetLastWhenEffect();
    checkArgument(effect != null);
    return effect;
  }

  @Nullable
  private Effect tryGetLastWhenEffect() {
    Effect effect = null;
    for (Object event : getEvents()) {
      if (event instanceof Effect) {
        effect = (Effect) event;
        break;
      }
    }
    return effect;
  }

  private static class Mocking {
    Object mock;
  }

  public void logMocking(Object mock) {
    checkNotNull(mock);
    Mocking mocking = new Mocking();
    mocking.mock = mock;
    addEvent(mocking);
  }

  public boolean isMock(Object mock) {
    checkNotNull(mock);
    for (Object event : getEvents()) {
      if (event instanceof Mocking) {
        Mocking mocking = (Mocking) event;
        if (mocking.mock == mock) {
          return true;
        }
      }
    }
    return false;
  }

  private static class Stubbing {
    Handler handler;
    InvocationMatcher invocationMatcher;
  }

  public void logStubbing(Handler handler, InvocationMatcher invocationMatcher) {
    Stubbing stubbing = new Stubbing();
    stubbing.handler = handler;
    stubbing.invocationMatcher = invocationMatcher;
    addEvent(stubbing);
  }

  public boolean hasStubbedHandlerFor(Invocation invocation) {
    return tryGetStubbedHandlerFor(invocation) != null;
  }

  public Handler getStubbedHandlerFor(Invocation invocation) {
    Handler handler = tryGetStubbedHandlerFor(invocation);
    checkArgument(handler != null);
    return handler;
  }

  @Nullable
  private Handler tryGetStubbedHandlerFor(Invocation invocation) {
    for (Object event : getEvents()) {
      if (event instanceof Stubbing) {
        Stubbing stubbing = (Stubbing) event;
        if (stubbing.invocationMatcher.matches(invocation)) {
          return stubbing.handler;
        }
      }
    }
    return null;
  }

  public void logInvocation(Invocation invocation) {
    addEvent(invocation);
  }

  public List<Invocation> getInvocations() {
    List<Invocation> invocations = new ArrayList<Invocation>();
    for (Object event : getEvents()) {
      if (event instanceof Invocation) {
        invocations.add((Invocation) event);
      }
    }
    return invocations;
  }

  public void logAny(Any any) {
    addEvent(any);
  }

  public List<Any> getAnysAndConsume() {
    class Consumer {}
    List<Any> anys = new ArrayList<Any>();
    for (Object event : getEvents()) {
      if (event instanceof Any) {
        anys.add(0, (Any) event);
      } else if (event instanceof Consumer) {
        break;
      }
    }
    addEvent(new Consumer());
    return anys;
  }
}

@@ -123,16 +123,70 @@
 NotNull;
+%0Aimport static org.testory.common.Collections.reverse;
 %0A%0Aimport
@@ -424,35 +424,8 @@
 y %7B%0A
-  /** youngest at begin */%0A
   pr
@@ -917,19 +917,16 @@
 t().add(
-0, 
 event);%0A
@@ -1008,17 +1008,23 @@
 ct%3E 
-e
+latestE
 vents = 
 getE
@@ -1019,16 +1019,24 @@
 vents = 
+reverse(
 getEvent
@@ -1038,16 +1038,17 @@
 Events()
+)
 ;%0A    fo
@@ -1065,17 +1065,23 @@
  0; i %3C 
-e
+latestE
 vents.si
@@ -1099,25 +1099,31 @@
 %7B%0A      if (
-e
+latestE
 vents.get(i)
@@ -1161,17 +1161,31 @@
 tEvents(
-e
+reverse(latestE
 vents.su
@@ -1196,16 +1196,17 @@
 t(0, i))
+)
 ;%0A      
@@ -1748,32 +1748,40 @@
 (Object event : 
+reverse(
 getEvents()) %7B%0A 
@@ -1768,32 +1768,33 @@
 rse(getEvents())
+)
  %7B%0A      if (eve
@@ -3139,32 +3139,40 @@
 (Object event : 
+reverse(
 getEvents()) %7B%0A 
@@ -3159,32 +3159,33 @@
 rse(getEvents())
+)
  %7B%0A      if (eve
@@ -3938,32 +3938,40 @@
 (Object event : 
+reverse(
 getEvents()) %7B%0A 
@@ -3958,32 +3958,33 @@
 rse(getEvents())
+)
  %7B%0A      if (eve

refactored event list to be in chronological order