Index: protocol/pom.xml
===================================================================
--- protocol/pom.xml	(revision 30)
+++ protocol/pom.xml	(revision 31)
@@ -6,5 +6,5 @@
 	<groupId>geniusweb</groupId>
 	<artifactId>protocol</artifactId>
-	<version>1.5.9</version> <!-- must equal ${geniusweb.version} -->
+	<version>1.6.0</version> <!-- must equal ${geniusweb.version} -->
 	<packaging>jar</packaging>
 
@@ -17,5 +17,5 @@
 		<passwd>${env.ARTIFACTORY_PASS}</passwd>
 		<jackson-2-version>2.9.10</jackson-2-version>
-		<geniusweb.version>1.5.9</geniusweb.version>
+		<geniusweb.version>1.6.0</geniusweb.version>
 	</properties>
 
Index: protocol/src/main/java/geniusweb/protocol/NegoSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/NegoSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/NegoSettings.java	(revision 31)
@@ -5,10 +5,8 @@
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 import geniusweb.protocol.session.SessionSettings;
 import geniusweb.protocol.tournament.TournamentSettings;
-
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-
 import tudelft.utilities.logging.Reporter;
 
@@ -36,5 +34,5 @@
 	/**
 	 * @param logger the logger where the protocol can log events to.
-	 * @return the an initialized and ready to use {@link NegoProtocol} that can
+	 * @return an initialized and ready to use {@link NegoProtocol} that can
 	 *         handle this Negotiation.
 	 */
Index: protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java	(revision 31)
@@ -1,5 +1,8 @@
 package geniusweb.protocol.session;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -10,32 +13,29 @@
 import geniusweb.progress.Progress;
 import geniusweb.protocol.ProtocolException;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.references.PartyWithProfile;
 
 /**
+ * 
+ */
+/**
+ * 
  * The default current state of the session. immutable.
+ * 
+ * @param <P> the actual SessionState object
+ * @param <S> the actual SessionSettings object
  */
-public abstract class DefaultSessionState implements SessionState {
+public abstract class DefaultSessionState<P extends DefaultSessionState<P, S>, S extends SessionSettings>
+		implements SessionState {
 	private final List<Action> actions;
 
-	private final transient ProtocolToPartyConnections connections;
+	private final List<PartyId> connections;
 
 	private final Progress progress;
 
-	private final SessionSettings settings;
+	private final S settings;
 
 	private final ProtocolException error;
 
 	private final Map<PartyId, PartyWithProfile> partyprofiles;
-
-	// only for deserializer.
-	protected DefaultSessionState() {
-		actions = null;
-		connections = null;
-		progress = null;
-		settings = null;
-		partyprofiles = null;
-		error = null;
-	}
 
 	/**
@@ -59,14 +59,14 @@
 	 *                      result in a throw and terminate the session.
 	 */
-	public DefaultSessionState(List<Action> actions,
-			ProtocolToPartyConnections conns, Progress progr,
-			SessionSettings settings,
+	public DefaultSessionState(List<Action> actions, List<PartyId> conns,
+			Progress progr, S settings,
 			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
 		if (partyprofiles == null) {
-			partyprofiles = Collections.emptyMap();
+			this.partyprofiles = Collections.emptyMap();
+		} else {
+			this.partyprofiles = partyprofiles;
 		}
 		if (conns == null) {
-			this.connections = new ProtocolToPartyConnections(
-					Collections.emptyList());
+			this.connections = Collections.emptyList();
 		} else {
 			this.connections = conns;
@@ -78,19 +78,41 @@
 		}
 
-		if (!connections.allunique()) {
+		if (connections.size() != new HashSet<PartyId>(connections).size()) {
 			throw new IllegalArgumentException(
 					"There can not be multiple connections for a party:"
 							+ connections);
 		}
+		if (settings == null)
+			throw new IllegalArgumentException("Settings must be not null");
 		this.progress = progr;
 		this.settings = settings;
-		this.partyprofiles = partyprofiles;
 		this.error = e;
 	}
 
 	/**
+	 * Construct a new session state, where the DefaultSessionState changes
+	 * while the rest of the state remains unchanged. Notice the return type P.
+	 * 
+	 * @param actions1       the new {@link Action}s
+	 * @param conns          the new connected {@link PartyId}s
+	 * @param progress1      the new {@link Progress}
+	 * @param settings1      the new {@link SessionSettings}. Normally this is
+	 *                       constant during a session.
+	 * @param partyprofiles1 the new {@link PartyWithProfile}s for all parties.
+	 *                       Normally this remains constant during a session.
+	 * @param e              an error that occured that caused the session to
+	 *                       reach its terminated/final state. If an error does
+	 *                       not cause termination, only a warning should be
+	 *                       logged.
+	 * @return the new state of the derived sessionstate.
+	 */
+	abstract public P with(List<Action> actions1, List<PartyId> conns,
+			Progress progress1, S settings1,
+			Map<PartyId, PartyWithProfile> partyprofiles1, ProtocolException e);
+
+	/**
 	 * @return existing connections.
 	 */
-	public ProtocolToPartyConnections getConnections() {
+	public List<PartyId> getConnections() {
 		return connections;
 	}
@@ -120,5 +142,5 @@
 
 	@Override
-	public SessionSettings getSettings() {
+	public S getSettings() {
 		return settings;
 	}
@@ -137,6 +159,86 @@
 	public String toString() {
 		return this.getClass().getSimpleName() + "[" + actions + ","
-				+ connections + "," + progress + "," + settings + "," + error
-				+ "]";
+				+ connections + "," + progress + "," + settings + "," + error;
+	}
+
+	public P withDeadlineReached() {
+		return with(actions, connections, progress, settings, partyprofiles,
+				error);
+	}
+
+	public P withoutParty(PartyId party) {
+		List<PartyId> newconn = new ArrayList<>(connections);
+		newconn.remove(party);
+		return with(actions, newconn, progress, settings, partyprofiles, error);
+	}
+
+	/**
+	 * 
+	 * @param e the error that occured
+	 * @return a new state with the error set/updated.
+	 */
+	public P with(ProtocolException e) {
+		return with(actions, connections, progress, settings, partyprofiles, e);
+	}
+
+	public P with(PartyId connection, PartyWithProfile partyprofile) {
+		List<PartyId> newconns = new ArrayList<>(getConnections());
+		newconns.add(connection);
+		Map<PartyId, PartyWithProfile> newprofiles = new HashMap<>(
+				getPartyProfiles());
+		newprofiles.put(connection, partyprofile);
+		return with(getActions(), newconns, getProgress(), getSettings(),
+				newprofiles, null);
+	}
+
+	/**
+	 * Sets the new progress for this session.
+	 * 
+	 * @param newprogress the new progress
+	 * @return new SAOPState with the progress set to new value
+	 */
+	public P with(Progress newprogress) {
+		if (newprogress == null) {
+			throw new IllegalArgumentException("newprogress must be not null");
+		}
+		return with(actions, connections, newprogress, settings, partyprofiles,
+				error);
+	}
+
+	/**
+	 * @param actor  the actor that did this action. Can be used to check if
+	 *               action is valid. NOTICE caller has to make sure the current
+	 *               state is not final.
+	 * @param action the action that was proposed by actor.
+	 * @return new SAOPState with the action added as last action.
+	 */
+
+	public P with(PartyId actor, Action action) {
+		String msg = checkAction(actor, action);
+		if (msg != null)
+			throw new IllegalArgumentException(msg);
+
+		List<Action> newactions = new LinkedList<>(getActions());
+		newactions.add(action);
+		return with(newactions, connections, progress, settings, partyprofiles,
+				error);
+	}
+
+	/**
+	 * 
+	 * @param actor  the known real actor that did this action
+	 * @param action an {@link Action}
+	 * @return null if action seems ok, or message explaining why not.
+	 */
+	public String checkAction(PartyId actor, Action action) {
+		if (actor == null) { // this is a bug
+			return "actor must not be null";
+		}
+		if (action == null) {
+			return "action is null";
+		}
+		if (!actor.equals(action.getActor()))
+			return "act contains wrong credentials: " + action;
+		return null;
 	}
 
Index: protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 31)
@@ -7,9 +7,9 @@
 import geniusweb.protocol.NegoSettings;
 import geniusweb.protocol.session.amop.AMOPSettings;
+import geniusweb.protocol.session.learn.LearnSettings;
 import geniusweb.protocol.session.mopac.MOPACSettings;
 import geniusweb.protocol.session.mopac2.MOPAC2Settings;
 import geniusweb.protocol.session.saop.SAOPSettings;
 import geniusweb.protocol.session.shaop.SHAOPSettings;
-import geniusweb.protocol.tournament.allpermutations.Team;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
@@ -22,9 +22,10 @@
 		@JsonSubTypes.Type(value = AMOPSettings.class),
 		@JsonSubTypes.Type(value = MOPACSettings.class),
-		@JsonSubTypes.Type(value = MOPAC2Settings.class) })
+		@JsonSubTypes.Type(value = MOPAC2Settings.class),
+		@JsonSubTypes.Type(value = LearnSettings.class) })
 public interface SessionSettings extends NegoSettings {
 
 	/**
-	 * Get the {@link Team}s.
+	 * Get the {@link TeamInfo}s.
 	 * 
 	 * @return list of {@link PartyWithProfile} items.
@@ -33,7 +34,7 @@
 
 	/**
-	 * @return the size of each {@link Team}. Usually 1 (SAOP etc) or sometimes
-	 *         2 (SHAOP). Used eg by tournament runner to determine proper team
-	 *         construction.
+	 * @return the size of each {@link TeamInfo}. Usually 1 (SAOP etc) or
+	 *         sometimes 2 (SHAOP). Used eg by tournament runner to determine
+	 *         proper team construction.
 	 */
 	Integer getTeamSize();
Index: protocol/src/main/java/geniusweb/protocol/session/SessionState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/SessionState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/SessionState.java	(revision 31)
@@ -10,4 +10,5 @@
 import geniusweb.protocol.NegoState;
 import geniusweb.protocol.session.amop.AMOPState;
+import geniusweb.protocol.session.learn.LearnState;
 import geniusweb.protocol.session.mopac.MOPACState;
 import geniusweb.protocol.session.saop.SAOPState;
@@ -33,5 +34,6 @@
 		@JsonSubTypes.Type(value = SHAOPState.class),
 		@JsonSubTypes.Type(value = AMOPState.class),
-		@JsonSubTypes.Type(value = MOPACState.class) })
+		@JsonSubTypes.Type(value = MOPACState.class),
+		@JsonSubTypes.Type(value = LearnState.class) })
 public interface SessionState extends NegoState {
 
Index: protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java	(revision 31)
@@ -1,7 +1,9 @@
 package geniusweb.protocol.session;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
@@ -11,4 +13,6 @@
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
+import geniusweb.protocol.tournament.Team;
+import geniusweb.references.Parameters;
 import geniusweb.references.PartyWithParameters;
 import geniusweb.references.PartyWithProfile;
@@ -23,5 +27,5 @@
 public class TeamInfo {
 
-	private List<PartyWithProfile> parties;
+	private final List<PartyWithProfile> parties;
 
 	/**
@@ -86,3 +90,27 @@
 		return true;
 	}
+
+	/**
+	 * @param parameters a set of {@link Parameters} to be merged with the
+	 *                   existing parameters of the first team member.
+	 * @return updated TeamInfo
+	 */
+	public TeamInfo with(Parameters parameters) {
+		List<PartyWithProfile> updatedparties = new ArrayList<>(parties);
+		PartyWithProfile party1 = updatedparties.get(0);
+		Parameters newparams1 = party1.getParty().getParameters()
+				.with(parameters);
+		PartyWithProfile party1update = new PartyWithProfile(
+				party1.getParty().with(newparams1), party1.getProfile());
+		updatedparties.set(0, party1update);
+		return new TeamInfo(updatedparties);
+	}
+
+	/**
+	 * @return the Team, without the profiles
+	 */
+	public Team getTeam() {
+		return new Team(parties.stream().map(pwithp -> pwithp.getParty())
+				.collect(Collectors.toList()));
+	}
 }
Index: protocol/src/main/java/geniusweb/protocol/session/amop/AMOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/amop/AMOP.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/amop/AMOP.java	(revision 31)
@@ -48,8 +48,8 @@
  * specified initially in the settings do participate.
  * 
- * <h1>parameter</h1> AMOP parties can receive a parameter: minVotes containing
+ * <h2>parameter</h2> AMOP parties can receive a parameter: minVotes containing
  * a Double. If set, the AMOP protocol checks that all {@link Votes} from that
  * party have {@link Vote#getMinPower()} &gt; minVotes.
- * <h1>Protocol steps</h1>
+ * <h2>Protocol steps</h2>
  * <ol>
  * <li>The protocol tries to start all parties. If not all parties start, the
Index: protocol/src/main/java/geniusweb/protocol/session/learn/Learn.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/learn/Learn.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/session/learn/Learn.java	(revision 31)
@@ -0,0 +1,310 @@
+package geniusweb.protocol.session.learn;
+
+import java.io.IOException;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.PartyId;
+import geniusweb.deadline.Deadline;
+import geniusweb.events.ProtocolEvent;
+import geniusweb.inform.Finished;
+import geniusweb.inform.Inform;
+import geniusweb.inform.Settings;
+import geniusweb.progress.ProgressFactory;
+import geniusweb.protocol.CurrentNegoState;
+import geniusweb.protocol.NegoState;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import geniusweb.references.ProtocolRef;
+import geniusweb.references.Reference;
+import tudelft.utilities.listener.DefaultListenable;
+import tudelft.utilities.logging.Reporter;
+import tudelft.utilities.repository.NoResourcesNowException;
+
+/**
+ * The Learn protocol allows parties to learn until the deadline set in the
+ * {@link LearnSettings}.
+ */
+public class Learn extends DefaultListenable<ProtocolEvent>
+		implements SessionProtocol {
+	public static final int MIN_SLEEP_TIME = 1000;
+	public static final int MAX_SLEEP_TIME = 60000;
+	public static final int TIME_MARGIN = 20;// ms extra delay after deadline
+	private LearnState state;
+	private final Reporter log;
+	private static final ProtocolRef LEARN = new ProtocolRef("Learn");
+	private volatile Timer deadlinetimer = null;
+	private volatile AtomicBoolean isFinishedInfoSent = new AtomicBoolean(
+			false);
+	private volatile ProtocolToPartyConnections conns = new ProtocolToPartyConnections(
+			Collections.emptyList());
+
+	public Learn(LearnState state, Reporter logger) {
+		this.state = state;
+		this.log = logger;
+	}
+
+	@Override
+	public void start(ProtocolToPartyConnFactory connectionfactory) {
+		try {
+			connect(connectionfactory);
+			setDeadline();
+			setupParties();
+		} catch (Throwable e) {
+			handleError("Failed to start up session", null, e);
+		}
+
+	}
+
+	@Override
+	public String getDescription() {
+		return "Sends all parties a ";
+	}
+
+	@Override
+	public NegoState getState() {
+		return state;
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return LEARN;
+	}
+
+	@Override
+	public void addParticipant(PartyWithProfile party) {
+		throw new IllegalStateException(
+				"Dynamic joining a negotiation is not allowed in LEARN");
+	}
+
+	/*******************************************************************
+	 * private functions. Some are protected only, for testing purposes
+	 ********************************************************************/
+	/**
+	 * step 1 in protocol: connect all involved parties and start the clock.
+	 * This always "succeeds" with a valid (but possibly final) state
+	 * <p>
+	 * This is 'protected' to allow junit testing, this code is not a 'public'
+	 * part of the interface.
+	 * 
+	 * @param connectionfactory the connectionfactory for making party
+	 *                          connections
+	 * 
+	 * @throws InterruptedException if the connection procedure is unterrupted
+	 * 
+	 * @throws IOException          if this fails to properly conect to the
+	 *                              parties, eg interrupted or server not
+	 *                              responding..
+	 */
+	protected synchronized void connect(
+			ProtocolToPartyConnFactory connectionfactory)
+			throws InterruptedException, IOException {
+		List<PartyWithProfile> participants = state.getSettings()
+				.getAllParties();
+		List<Reference> parties = participants.stream()
+				.map(parti -> (parti.getParty().getPartyRef()))
+				.collect(Collectors.toList());
+		List<ProtocolToPartyConn> connections = null;
+		log.log(Level.INFO, "SAOP connect " + parties);
+		while (connections == null) {
+			try {
+				connections = connectionfactory.connect(parties);
+			} catch (NoResourcesNowException e) {
+				long waitms = e.getLater().getTime()
+						- System.currentTimeMillis();
+				log.log(Level.INFO,
+						"No resources available to run session, waiting"
+								+ waitms);
+				Thread.sleep(Math.min(MAX_SLEEP_TIME,
+						Math.max(MIN_SLEEP_TIME, waitms)));
+			}
+		}
+		for (int i = 0; i < participants.size(); i++) {
+			conns = conns.with(connections.get(i));
+			setState(this.state.with(connections.get(i).getParty(),
+					participants.get(i)));
+		}
+	}
+
+	/**
+	 * This is called when one of the party connections does an action.
+	 * Synchronized so that we always handle only 1 action at a time.
+	 * 
+	 * @param partyconn the connection on which the action came in.
+	 * @param action    the {@link Action} taken by some party
+	 */
+	protected synchronized void actionRequest(
+			final ProtocolToPartyConn partyconn, final Action action) {
+		if (action == null) {
+			Throwable err = partyconn.getError();
+			if (err == null) {
+				err = new ProtocolException("Party sent a null action",
+						partyconn.getParty());
+			}
+			handleError(partyconn + "Protocol error", partyconn.getParty(),
+					err);
+			return;
+		}
+
+		try {
+			setState(state.with(partyconn.getParty(), action));
+		} catch (Throwable e) {
+			handleError("failed to handle action " + action,
+					partyconn.getParty(), e);
+		}
+
+	}
+
+	/**
+	 * step 2 in protocol: listen to connections and send settings to the
+	 * parties.
+	 * <p>
+	 * This is 'protected' to allow junit testing, this code is not a 'public'
+	 * part of the interface.
+	 * 
+	 * @throws ProtocolException if a party does not follow the protocol
+	 */
+	protected synchronized void setupParties() throws ProtocolException {
+		for (ProtocolToPartyConn conn : conns) {
+			conn.addListener(action -> actionRequest(conn, action));
+		}
+
+		for (ProtocolToPartyConn connection : conns) {
+			try {
+				sendSettings(connection);
+			} catch (IOException e) {
+				throw new ProtocolException("Failed to initialize",
+						connection.getParty(), e);
+			}
+		}
+
+	}
+
+	/**
+	 * Inform a party about its settings
+	 * 
+	 * @param connection
+	 * @throws IOException if party got disconnected
+	 */
+	private synchronized void sendSettings(ProtocolToPartyConn connection)
+			throws IOException {
+		PartyId partyid = connection.getParty();
+		ProfileRef profile = state.getPartyProfiles().get(partyid).getProfile();
+		Parameters params = state.getPartyProfiles().get(partyid).getParty()
+				.getParameters();
+		if (profile == null) {
+			throw new IllegalArgumentException(
+					"Missing profile for party " + connection.getReference());
+		}
+		connection.send(new Settings(connection.getParty(), profile, getRef(),
+				state.getProgress(), params));
+	}
+
+	/**
+	 * Set state to proper deadline. Starts the timer tasks. This tasks triggers
+	 * a call to handleError when the session times out.
+	 */
+	private synchronized void setDeadline() {
+		long now = System.currentTimeMillis();
+		Deadline deadline = state.getSettings().getDeadline();
+		setState(state.with(ProgressFactory.create(deadline, now)));
+		deadlinetimer = new Timer();
+		TimerTask task = new TimerTask() {
+			@Override
+			public void run() {
+				if (!state.isFinal(System.currentTimeMillis())) {
+					log.log(Level.SEVERE,
+							"BUG. Deadline timer has triggered but state is not final");
+				}
+				log.log(Level.WARNING,
+						"SAOP deadline reached. Terminating session.");
+				finish();
+			}
+		};
+		// set timer TIME_MARGIN after real deadline to ensure we're not too
+		// early
+		deadlinetimer.schedule(task, deadline.getDuration() + TIME_MARGIN);
+		log.log(Level.INFO, "SAOP deadline set to "
+				+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+						.format(new Date(System.currentTimeMillis()
+								+ deadline.getDuration() + TIME_MARGIN)));
+	}
+
+	/**
+	 * Update state to include the given error and finishes up the session.
+	 * 
+	 * @param message The message to attach to the error
+	 * @param party   the party where the error occured
+	 * @param e       the exception that occured.
+	 */
+	private synchronized void handleError(final String message,
+			final PartyId party, final Throwable e) {
+		if (e instanceof ProtocolException) {
+			setState(state.with((ProtocolException) e));
+		} else {
+			setState(state.with(new ProtocolException(message, party, e)));
+		}
+		log.log(Level.WARNING, "SAOP protocol intercepted error due to party "
+				+ party + ":" + message, e);
+	}
+
+	/**
+	 * Sets the new state. If the new state is final, the finish-up procedure is
+	 * executed.
+	 * 
+	 * @param newstate the new state.
+	 */
+	private synchronized void setState(LearnState newstate) {
+		long now = System.currentTimeMillis();
+		if (state.isFinal(now)) {
+			finish();
+			return;
+		}
+		this.state = newstate;
+		if (newstate.isFinal(now)) {
+			finish();
+		}
+	}
+
+	/**
+	 * Called when we reach final state. Cancels deadline timer. Send finished
+	 * info to all parties, notify current nego state as final and set
+	 * {@link #isFinishedInfoSent}. Double calls are automatically ignored.
+	 */
+	private synchronized void finish() {
+		if (deadlinetimer != null) {
+			deadlinetimer.cancel();
+			deadlinetimer = null;
+		}
+		if (!isFinishedInfoSent.compareAndSet(false, true))
+			return;
+		Inform finished = new Finished(state.getAgreements());
+		for (ProtocolToPartyConn conn : conns) {
+			sendFinish(conn, finished);
+		}
+		notifyListeners(new CurrentNegoState(state));
+	}
+
+	private void sendFinish(ProtocolToPartyConn connection, Inform finished) {
+		try {
+			connection.send(finished);
+			connection.close();
+		} catch (Exception e) {
+			log.log(Level.INFO, "Failed to send Finished to " + connection, e);
+		}
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/session/learn/LearnSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/learn/LearnSettings.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/session/learn/LearnSettings.java	(revision 31)
@@ -0,0 +1,181 @@
+package geniusweb.protocol.session.learn;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.FileLocation;
+import geniusweb.deadline.Deadline;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.protocol.session.saop.SAOPSettings;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * Settings for learn protocol.
+ */
+public class LearnSettings implements SessionSettings {
+	private final List<TeamInfo> participants;
+	private final Deadline deadline;
+
+	private final static ObjectMapper jackson = new ObjectMapper();
+
+	/**
+	 * 
+	 * @param participants the list of {@link PartyWithProfile} in clockwise
+	 *                     order. There must be at least 2 to run the SAOP
+	 *                     protocol. But SAOP can be initialized with less, for
+	 *                     use in TournamentSettings.
+	 *                     <table>
+	 *                     <caption>Required parameters</caption>
+	 *                     <tr>
+	 *                     <td>persistentstate</td>
+	 *                     <td>a {@link FileLocation}, serialized as json eg
+	 *                     <code>"b29f3bf5-1dc4-499e-a676-7b2dbb864a03"</code> ,
+	 *                     where the party's persistant state is stored; this
+	 *                     must match the persistantstate used in earlier calls
+	 *                     for e.g. {@link SAOPSettings}.</td>
+	 *                     </tr>
+	 * 
+	 *                     <tr>
+	 *                     <td>negotiationdata</td>
+	 *                     <td>a List of {@link FileLocation}s, serialized as
+	 *                     json, matching the negotiationdata used in earlier
+	 *                     calls for eg {@link SAOPSettings}.</td>
+	 *                     </tr>
+	 *                     </table>
+	 * @param deadline     the deadline of the negotiation.
+	 */
+	@SuppressWarnings("unused")
+	@JsonCreator
+	public LearnSettings(
+			@JsonProperty("participants") List<TeamInfo> participants,
+			@JsonProperty("deadline") Deadline deadline) {
+		this.participants = participants;
+		this.deadline = deadline;
+		if (participants == null) {
+			throw new IllegalArgumentException("participants must not be null");
+		}
+		if (deadline == null) {
+			throw new IllegalArgumentException("deadline must not be null");
+		}
+
+		for (PartyWithProfile pwithp : getAllParties()) {
+			Parameters params = pwithp.getParty().getParameters();
+			Object statestr = params.get("persistentstate");
+			if (!(statestr instanceof String))
+				throw new IllegalArgumentException(
+						"persistentstate parameter containing UUID string is required, but found "
+								+ statestr);
+			// check that it contains a UUID. Don't store it, we don't need
+			// it.
+			UUID.fromString((String) statestr);
+
+			// and check the negotiationdata
+			Object negotiationsstr = params.get("negotiationdata");
+			if (!(negotiationsstr instanceof List))
+				throw new IllegalArgumentException(
+						"negotiationdata parameter containing a List is required");
+
+			for (Object negostr : (List) negotiationsstr) {
+				if (!(negostr instanceof String))
+					throw new IllegalArgumentException(
+							"The list in negotiationdata must contain UUID strings but found "
+									+ negostr);
+				UUID.fromString((String) negostr);
+			}
+		}
+	}
+
+	@Override
+	public Double getMaxRunTime() {
+		return deadline.getDuration() / 1000d;
+	}
+
+	@Override
+	public SessionProtocol getProtocol(Reporter logger) {
+		return new Learn(new LearnState(this), logger);
+	}
+
+	@Override
+	public List<TeamInfo> getTeams() {
+		return Collections.unmodifiableList(participants);
+	}
+
+	/**
+	 * @return the deadline for this negotiation
+	 */
+	public Deadline getDeadline() {
+		return deadline;
+	}
+
+	@Override
+	public List<PartyWithProfile> getAllParties() {
+		return participants.stream()
+				.map(particip -> particip.getAllParties().get(0))
+				.collect(Collectors.toList());
+	}
+
+	@Override
+	public Integer getTeamSize() {
+		return 1;
+	}
+
+	@Override
+	public String toString() {
+		return "LearnSettings" + participants + "," + deadline + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((deadline == null) ? 0 : deadline.hashCode());
+		result = prime * result
+				+ ((participants == null) ? 0 : participants.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LearnSettings other = (LearnSettings) obj;
+		if (deadline == null) {
+			if (other.deadline != null)
+				return false;
+		} else if (!deadline.equals(other.deadline))
+			return false;
+		if (participants == null) {
+			if (other.participants != null)
+				return false;
+		} else if (!participants.equals(other.participants))
+			return false;
+		return true;
+	}
+
+	@Override
+	public SessionSettings with(TeamInfo team) {
+		if (team.getSize() != 1)
+			throw new IllegalArgumentException(
+					"Team must be size 1 but found " + team);
+		List<TeamInfo> newparts = new LinkedList<>(participants);
+		newparts.add(team);
+		return new LearnSettings(newparts, deadline);
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/learn/LearnState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/learn/LearnState.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/session/learn/LearnState.java	(revision 31)
@@ -0,0 +1,126 @@
+package geniusweb.protocol.session.learn;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.LearningDone;
+import geniusweb.actions.PartyId;
+import geniusweb.inform.Agreements;
+import geniusweb.progress.Progress;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.DefaultSessionState;
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.references.PartyWithProfile;
+
+/**
+ * Stores the state for Learn protocol. Immutable.
+ */
+public class LearnState extends DefaultSessionState<LearnState, LearnSettings> {
+
+	/**
+	 * 
+	 * @param actions       the actions done by the parties
+	 * @param conns         the existing party connections. we assume ownership
+	 *                      of this so it should not be modified although
+	 *                      connections may of course break. If null, default
+	 *                      empty list is used. Can be less than 2 parties in
+	 *                      the first phases of the setup.
+	 * @param progress      the {@link Progress} line. can be null
+	 * @param settings      the {@link LearnSettings}
+	 * @param partyprofiles map with the {@link PartyWithProfile} for connected
+	 *                      parties. null is equivalent to an empty map.
+	 * @param e             the {@link ProtocolException}, or null if none
+	 *                      occurred.
+	 */
+	public LearnState(List<Action> actions, List<PartyId> conns,
+			Progress progress, LearnSettings settings,
+			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
+		super(actions, conns, progress, settings, partyprofiles, e);
+	}
+
+	/**
+	 * Creates the initial state from the given settings and progress=null
+	 * 
+	 * @param settings the {@link LearnSettings}
+	 */
+	public LearnState(LearnSettings settings) {
+		this(Collections.emptyList(), Collections.emptyList(), null, settings,
+				null, null);
+
+	}
+
+	@Override
+	public LearnState with(List<Action> actions, List<PartyId> conns,
+			Progress progr, LearnSettings settings,
+			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
+		return new LearnState(actions, conns, progr, settings, partyprofiles,
+				e);
+	}
+
+	@Override
+	public Agreements getAgreements() {
+		return new Agreements();
+	}
+
+	@Override
+	public SessionResult getResult() {
+		return new SessionResult(getPartyProfiles(), getAgreements(),
+				Collections.emptyMap(), null);
+	}
+
+	@Override
+	public boolean isFinal(long currentTimeMs) {
+		Set<PartyId> done = getDoneLearning();
+		// if done is empty we're probably still starting up.
+		return super.isFinal(currentTimeMs)
+				|| (!done.isEmpty() && done.containsAll(getConnections()));
+	}
+
+	/**
+	 * 
+	 * @param e the error that occured
+	 * @return a new state with the error set.
+	 */
+	@Override
+	public LearnState with(ProtocolException e) {
+		return new LearnState(getActions(), getConnections(), getProgress(),
+				getSettings(), getPartyProfiles(), e);
+	}
+
+	@Override
+	public LearnSettings getSettings() {
+		return super.getSettings();
+	}
+
+	/**
+	 * 
+	 * @param actor  the known real actor that did this action
+	 * @param action an {@link Action}
+	 * @return null if action seems ok, or message explaining why not.
+	 */
+	@Override
+	public String checkAction(PartyId actor, Action action) {
+		String msg = super.checkAction(actor, action);
+		if (msg != null)
+			return msg;
+		if (getDoneLearning().contains(actor))
+			return "actor already was done learning";
+		if (!(action instanceof LearningDone))
+			return "Action " + action + " is not allowed";
+		return null;
+	}
+
+	/**
+	 * 
+	 * @return set of parties that reported they are done with learning.
+	 */
+	private Set<PartyId> getDoneLearning() {
+		return getActions().stream().map(party -> party.getActor())
+				.collect(Collectors.toSet());
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac/MOPAC.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac/MOPAC.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac/MOPAC.java	(revision 31)
@@ -46,9 +46,9 @@
  * specified initially in the settings do participate (no late joining in)
  * 
- * <h1>parameter</h1> MOPAC parties can receive a parameter <code>power</code>
+ * <h2>parameter</h2> MOPAC parties can receive a parameter <code>power</code>
  * containing a Integer. This parameter is picked up by this protocol, for
  * handling the agreement extraction from the offers. Power=1 for parties that
  * do not have the power parameter.
- * <h1>Protocol steps</h1>
+ * <h2>Protocol steps</h2>
  * 
  * <ol>
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java	(revision 31)
@@ -46,9 +46,9 @@
  * specified initially in the settings do participate (no late joining in)
  * 
- * <h1>parameter</h1> MOPAC parties can receive a parameter <code>power</code>
+ * <h2>parameter</h2> MOPAC parties can receive a parameter <code>power</code>
  * containing a Integer. This parameter is picked up by this protocol, for
  * handling the agreement extraction from the offers. Power=1 for parties that
  * do not have the power parameter.
- * <h1>Protocol steps</h1>
+ * <h2>Protocol steps</h2>
  * 
  * <ol>
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 31)
@@ -4,4 +4,5 @@
 import java.sql.Date;
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.List;
 import java.util.Timer;
@@ -25,4 +26,5 @@
 import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
 import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionSettings;
@@ -72,15 +74,13 @@
 	private final Reporter log;
 
+	// these 4 variables are why protocols are so complex.
 	private SAOPState state = null; // mutable!
 	private volatile AtomicBoolean isFinishedInfoSent = new AtomicBoolean(
 			false);
 	private volatile Timer deadlinetimer = null;
-
-	/**
-	 * 
-	 * @param state  normally the initial state coming from SAOPSettings
-	 * @param logger the Reporter to log to
-	 */
-	public SAOP(SAOPState state, Reporter logger) {
+	private volatile ProtocolToPartyConnections conns;
+
+	public SAOP(SAOPState state, Reporter logger,
+			ProtocolToPartyConnections connects) {
 		if (state == null) {
 			throw new NullPointerException("state must be not null");
@@ -92,4 +92,15 @@
 		this.log = logger;
 		this.state = state;
+		this.conns = connects;
+	}
+
+	/**
+	 * 
+	 * @param state  normally the initial state coming from SAOPSettings
+	 * @param logger the Reporter to log to
+	 */
+	public SAOP(SAOPState state, Reporter logger) {
+		this(state, logger,
+				new ProtocolToPartyConnections(Collections.emptyList()));
 	}
 
@@ -175,5 +186,9 @@
 		}
 		for (int i = 0; i < participants.size(); i++) {
-			setState(this.state.with(connections.get(i), participants.get(i)));
+			// now we bookkeep the connections ourselves,
+			// and update the state to keep in sync.
+			conns = conns.with(connections.get(i));
+			setState(this.state.with(connections.get(i).getParty(),
+					participants.get(i)));
 		}
 	}
@@ -219,9 +234,9 @@
 	 */
 	protected synchronized void setupParties() throws ProtocolException {
-		for (ProtocolToPartyConn conn : state.getConnections()) {
+		for (ProtocolToPartyConn conn : conns) {
 			conn.addListener(action -> actionRequest(conn, action));
 		}
 
-		for (ProtocolToPartyConn connection : state.getConnections()) {
+		for (ProtocolToPartyConn connection : conns) {
 			try {
 				sendSettings(connection);
@@ -282,5 +297,5 @@
 			}
 			// FIXME? this ignores possible broadcast errors
-			state.getConnections().broadcast(new ActionDone(action));
+			conns.broadcast(new ActionDone(action));
 			setState(state.with(partyconn.getParty(), action));
 			if (!state.isFinal(System.currentTimeMillis()))
@@ -298,8 +313,8 @@
 	 * @throws IOException
 	 */
-	private synchronized void nextTurn() {
+	synchronized void nextTurn() {
 		PartyId party = state.getNextActor();
 		try {
-			state.getConnections().get(party).send(new YourTurn());
+			conns.get(party).send(new YourTurn());
 		} catch (IOException e) {
 			handleError("failed to send YourTurn", party, e);
@@ -318,7 +333,9 @@
 		if (e instanceof ProtocolException) {
 			setState(state.with((ProtocolException) e));
+
 		} else {
 			setState(state.with(new ProtocolException(message, party, e)));
 		}
+		setState(state.withoutParty(party));
 		log.log(Level.WARNING, "SAOP protocol intercepted error due to party "
 				+ party + ":" + message, e);
@@ -356,5 +373,5 @@
 			return;
 		Inform finished = new Finished(state.getAgreements());
-		for (ProtocolToPartyConn conn : state.getConnections()) {
+		for (ProtocolToPartyConn conn : conns) {
 			sendFinish(conn, finished);
 		}
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 31)
@@ -119,6 +119,5 @@
 			throw new IllegalArgumentException(
 					"Team must be size 1 but found " + team);
-		List<TeamInfo> newparts = new LinkedList<>(
-				participants);
+		List<TeamInfo> newparts = new LinkedList<>(participants);
 		newparts.add(team);
 		return new SAOPSettings(newparts, deadline);
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 31)
@@ -2,10 +2,7 @@
 
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import geniusweb.actions.Accept;
@@ -19,9 +16,6 @@
 import geniusweb.progress.ProgressRounds;
 import geniusweb.protocol.ProtocolException;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.protocol.session.DefaultSessionState;
 import geniusweb.protocol.session.SessionResult;
-import geniusweb.protocol.session.SessionSettings;
 import geniusweb.references.PartyWithProfile;
 
@@ -29,14 +23,13 @@
  * Immutable.
  */
-public class SAOPState extends DefaultSessionState {
+public class SAOPState extends DefaultSessionState<SAOPState, SAOPSettings> {
 
 	/**
 	 * 
 	 * @param actions       the actions done by the parties
-	 * @param conns         the existing party connections. we assume ownership
-	 *                      of this so it should not be modified although
-	 *                      connections may of course break. If null, default
+	 * @param conns         the existing party connections. If null, default
 	 *                      empty list is used. Can be less than 2 parties in
-	 *                      the first phases of the setup.
+	 *                      the first phases of the setup or after parties
+	 *                      disconnected.
 	 * @param progress      the {@link Progress} line. can be null
 	 * @param settings      the {@link SAOPSettings}
@@ -46,8 +39,15 @@
 	 *                      occurred.
 	 */
-	public SAOPState(List<Action> actions, ProtocolToPartyConnections conns,
-			Progress progress, SessionSettings settings,
+	public SAOPState(List<Action> actions, List<PartyId> conns,
+			Progress progress, SAOPSettings settings,
 			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
 		super(actions, conns, progress, settings, partyprofiles, e);
+	}
+
+	@Override
+	public SAOPState with(List<Action> actions, List<PartyId> conns,
+			Progress progr, SAOPSettings settings,
+			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
+		return new SAOPState(actions, conns, progr, settings, partyprofiles, e);
 	}
 
@@ -58,57 +58,7 @@
 	 */
 	public SAOPState(SAOPSettings settings) {
-		this(Collections.emptyList(),
-				new ProtocolToPartyConnections(Collections.emptyList()), null,
-				settings, null, null);
+		this(Collections.emptyList(), Collections.emptyList(), null, settings,
+				null, null);
 
-	}
-
-	/**
-	 * 
-	 * @param connection   the new {@link ProtocolToPartyConn}
-	 * @param partyprofile the {@link PartyWithProfile} that is associated with
-	 *                     this state
-	 * @return new SessionState with the new connection added. This call ignores
-	 *         the progress (does not check isFinal) because SAOP uses this
-	 *         during the setup where the deadline is not yet relevant. The
-	 *         connection should be informed about the partyprofile but the
-	 *         state currently indicate if that already happened or not.
-	 */
-	public SAOPState with(ProtocolToPartyConn connection,
-			PartyWithProfile partyprofile) {
-		// Only called from the SAOP initialization phase.
-		ProtocolToPartyConnections newconns = getConnections().with(connection);
-		Map<PartyId, PartyWithProfile> newprofiles = new HashMap<>(
-				getPartyProfiles());
-		newprofiles.put(connection.getParty(), partyprofile);
-		return new SAOPState(getActions(), newconns, getProgress(),
-				getSettings(), newprofiles, null);
-	}
-
-	/**
-	 * 
-	 * @param e the error that occured
-	 * @return a new state with the error set.
-	 */
-	public SAOPState with(ProtocolException e) {
-		return new SAOPState(getActions(), getConnections(), getProgress(),
-				getSettings(), getPartyProfiles(), e);
-	}
-
-	/**
-	 * Sets the progress for this session. Can be set only if progress=null.
-	 * 
-	 * @param newprogress the new progress
-	 * @return new SAOPState with the progress set
-	 */
-	public SAOPState with(Progress newprogress) {
-		if (newprogress == null) {
-			throw new IllegalArgumentException("newprogress must be not null");
-		}
-		if (getProgress() != null) {
-			return this;
-		}
-		return new SAOPState(getActions(), getConnections(), newprogress,
-				getSettings(), getPartyProfiles(), getError());
 	}
 
@@ -131,5 +81,6 @@
 						&& bid.equals(((Accept) act).getBid()));
 		if (allaccept)
-			agree = agree.with(new Agreements(bid, getParties()));
+			agree = agree
+					.with(new Agreements(bid, new HashSet<>(getParties())));
 		return agree;
 	}
@@ -139,7 +90,6 @@
 	 * @return all currently connected parties.
 	 */
-	private Set<PartyId> getParties() {
-		return getConnections().stream().map(conn -> conn.getParty())
-				.collect(Collectors.toSet());
+	public List<PartyId> getParties() {
+		return getConnections();
 	}
 
@@ -153,59 +103,33 @@
 
 	@Override
-	public SAOPSettings getSettings() {
-		return (SAOPSettings) super.getSettings();
+	public SAOPState with(PartyId actor, Action action) {
+		return super.with(actor, action).with(advanceProgress());
 	}
 
-	/**
-	 * @param actor  the actor that did this action. Can be used to check if
-	 *               action is valid. NOTICE caller has to make sure the current
-	 *               state is not final.
-	 * @param action the action that was proposed by actor.
-	 * @return new SAOPState with the action added as last action.
-	 */
+	@Override
+	public String checkAction(PartyId actor, Action action) {
+		String msg = super.checkAction(actor, action);
+		if (msg != null)
+			return msg;
 
-	public SAOPState with(PartyId actor, Action action) {
-
-		if (actor == null) { // this is a bug
-			throw new IllegalArgumentException("actor must not be null");
-		}
-		if (!actor.equals(getNextActor())) {
-			throw new IllegalArgumentException("Party does not have the turn ");
-
-		}
-		if (action == null) {
-			throw new IllegalArgumentException("action is null");
-		}
-		if (!actor.equals(action.getActor())) {
-			throw new IllegalArgumentException(
-					"act contains wrong credentials: " + action);
-		}
+		if (!actor.equals(getNextActor()))
+			return "Party does not have the turn ";
 
 		// check protocol is followed for specific actions
 		if (action instanceof Accept) {
 			Bid bid = getLastBid();
-			if (bid == null) {
-				throw new IllegalArgumentException(
-						"Accept without a recent offer");
-			}
-			if (!bid.equals(((Accept) action).getBid())) {
-				throw new IllegalArgumentException(
-						"Party accepts a bid differing from the last offer ="
-								+ bid + ", action=" + action + ")");
-			}
+			if (bid == null)
+				return "Accept without a recent offer";
+
+			if (!bid.equals(((Accept) action).getBid()))
+				return "Party accepts a bid differing from the last offer ="
+						+ bid + ", action=" + action + ")";
+			return null;
 		} else if (action instanceof Offer) {
-			// offer is always fine. We don't check if bid is actually in domain
-			// or complete etc. We would need domain for that
+			return null;
 		} else if (action instanceof EndNegotiation) {
-			// just act, we will get into the final state then.
-		} else {
-			throw new IllegalArgumentException(
-					"Action " + action + " is not allowed in SAOP");
+			return null;
 		}
-
-		List<Action> newactions = new LinkedList<>(getActions());
-		newactions.add(action);
-		return new SAOPState(newactions, getConnections(), advanceProgress(),
-				getSettings(), getPartyProfiles(), null);
+		return "Action " + action + " is not allowed in SAOP";
 	}
 
@@ -232,17 +156,16 @@
 	 * 
 	 * @return the next actor in the current state. Assumes 1 action per actor
-	 *         every time.
+	 *         every time. NOTE if party disconnects this would jump wildly but
+	 *         nego stops then anyway
 	 */
 	protected PartyId getNextActor() {
 		return getConnections()
-				.get(getActions().size() % getConnections().size()).getParty();
+				.get(getActions().size() % getConnections().size());
 	}
 
 	/**
-	 * 
-	 * @param progress
 	 * @return new progress state
 	 */
-	private Progress advanceProgress() {
+	public Progress advanceProgress() {
 		Progress newprogress = getProgress();
 		if (newprogress instanceof ProgressRounds && isLastActor()) {
Index: protocol/src/main/java/geniusweb/protocol/tournament/ProfileList.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/ProfileList.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/ProfileList.java	(revision 31)
@@ -0,0 +1,68 @@
+package geniusweb.protocol.tournament;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import geniusweb.references.ProfileRef;
+
+/**
+ * Contains a list of profiles, one for each {@link Team} member.
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+public class ProfileList {
+	@JsonValue
+	private final List<ProfileRef> profiles; // field to ensure immutability
+
+	/**
+	 * 
+	 * @param ps a list of {@link ProfileRef}s. The length and order is
+	 *           important as each will be matched with the {@link Team} member
+	 *           at the same index.
+	 */
+	@JsonCreator
+	public ProfileList(List<ProfileRef> ps) {
+		this.profiles = new ArrayList<>(ps);
+	}
+
+	public List<ProfileRef> getProfiles() {
+		return Collections.unmodifiableList(profiles);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((profiles == null) ? 0 : profiles.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		ProfileList other = (ProfileList) obj;
+		if (profiles == null) {
+			if (other.profiles != null)
+				return false;
+		} else if (!profiles.equals(other.profiles))
+			return false;
+		return true;
+	}
+
+	public String toString() {
+		return profiles.toString();
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/Team.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/Team.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/Team.java	(revision 31)
@@ -0,0 +1,74 @@
+package geniusweb.protocol.tournament;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import geniusweb.references.PartyWithParameters;
+
+/**
+ * A team is a group of parties, each with their own parameters. Each will be
+ * coupled to a {@link ProfileList} in the generation phase of the protocol
+ *
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+public class Team {
+	@JsonValue
+	private final List<PartyWithParameters> parties; // ensure immutability
+
+	/**
+	 * 
+	 * @param ps a list of {@link PartyWithParameters}. Each party is always
+	 *           associated with the given parameters. The order of them is
+	 *           important as it will be matched with the order in the
+	 *           {@link ProfileList}.
+	 */
+	@JsonCreator
+	public Team(List<PartyWithParameters> ps) {
+		this.parties = new ArrayList<PartyWithParameters>(ps);
+	}
+
+	/**
+	 * 
+	 * @return list of all team {@link PartyWithParameters}.
+	 */
+	public List<PartyWithParameters> getParties() {
+		return Collections.unmodifiableList(parties);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((parties == null) ? 0 : parties.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Team other = (Team) obj;
+		if (parties == null) {
+			if (other.parties != null)
+				return false;
+		} else if (!parties.equals(other.parties))
+			return false;
+		return true;
+	}
+
+	public String toString() {
+		return parties.toString();
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/TournamentSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/TournamentSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/tournament/TournamentSettings.java	(revision 31)
@@ -5,4 +5,5 @@
 import geniusweb.protocol.NegoSettings;
 import geniusweb.protocol.tournament.allpermutations.AllPermutationsSettings;
+import geniusweb.protocol.tournament.allpermutationslearn.AllPermutationsLearnSettings;
 import tudelft.utilities.logging.Reporter;
 
@@ -10,5 +11,6 @@
  * Interface for tournament settings
  */
-@JsonSubTypes({ @JsonSubTypes.Type(value = AllPermutationsSettings.class) })
+@JsonSubTypes({ @JsonSubTypes.Type(value = AllPermutationsSettings.class),
+		@JsonSubTypes.Type(value = AllPermutationsLearnSettings.class) })
 public interface TournamentSettings extends NegoSettings {
 	@Override
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 31)
@@ -14,4 +14,6 @@
 import geniusweb.protocol.session.saop.SAOPSettings;
 import geniusweb.protocol.session.shaop.SHAOPSettings;
+import geniusweb.protocol.tournament.ProfileList;
+import geniusweb.protocol.tournament.Team;
 import geniusweb.protocol.tournament.TournamentProtocol;
 import geniusweb.protocol.tournament.TournamentSettings;
@@ -59,10 +61,10 @@
 public class AllPermutationsSettings implements TournamentSettings {
 
-	private final List<Team> teams;
-	private final List<ProfileList> profileslists;
-	private final boolean reuseTeams;
-	private final int teamsPerSession;
-	private final SessionSettings sessionsettings;
-	private final int numberTournaments;
+	protected final List<Team> teams;
+	protected final List<ProfileList> profileslists;
+	protected final boolean reuseTeams;
+	protected final int teamsPerSession;
+	protected final SessionSettings sessionsettings;
+	protected final int numberTournaments;
 
 	/**
@@ -81,7 +83,6 @@
 	 * @param teamsPerSession number of parties per session, must be at least 2.
 	 * @param sesettings      The generic {@link SessionSettings}.
-	 *                        {@link SessionSettings#with(TeamInfo)}
-	 *                        will be used to add the required
-	 *                        {@link TeamInfo}s
+	 *                        {@link SessionSettings#with(TeamInfo)} will be
+	 *                        used to add the required {@link TeamInfo}s
 	 * @param nTournaments    the number of times the tournament should be run.
 	 */
@@ -158,7 +159,11 @@
 	@Override
 	public String toString() {
-		return "AllPermutationsSettings[" + teams + "," + reuseTeams + ","
-				+ profileslists + "," + teamsPerSession + "," + sessionsettings
-				+ "," + numberTournaments + "]";
+		return "AllPermutationsSettings[" + contentString() + "]";
+	}
+
+	protected String contentString() {
+		return teams + "," + reuseTeams + "," + profileslists + ","
+				+ teamsPerSession + "," + sessionsettings + ","
+				+ numberTournaments;
 	}
 
@@ -216,5 +221,5 @@
 	 * @return {@link SessionSettings}
 	 */
-	private SessionSettings createSetting(
+	protected SessionSettings createSetting(
 			ImmutableList<TeamInfo> partyproflist) {
 		SessionSettings settings = sessionsettings;
@@ -227,14 +232,14 @@
 	/**
 	 * 
-	 * @param parties
-	 * @param profiles
-	 * @param n                    number of items to draw. Assumed >= 1.
+	 * @param parties              the parties in the tournament
+	 * @param profiles             the profiles in the tournament
+	 * @param n                    number of items to draw. Assumed &ge;1.
 	 * @param drawPartyWithPutback if parties can be drawn multiple times
 	 * @return list of sessionsets where each sessionset contains settings for a
-	 *         session: a list with {@link TeamInfo}s. The
-	 *         sessionsets are made by making all combinations of parties and
-	 *         profiles. Profiles are drawn with replace.
-	 */
-	private ImmutableList<ImmutableList<TeamInfo>> getParticipants(
+	 *         session: a list with {@link TeamInfo}s. The sessionsets are made
+	 *         by making all combinations of parties and profiles. Profiles are
+	 *         drawn with replace.
+	 */
+	protected ImmutableList<ImmutableList<TeamInfo>> getParticipants(
 			ImmutableList<Team> parties, ImmutableList<ProfileList> profiles,
 			int n, boolean drawPartyWithPutback) {
@@ -257,7 +262,6 @@
 		MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamInfo>> tournamentsettings = new MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamInfo>>(
 				tuple -> teamlist(tuple), tuples);
-		return new Repeat<ImmutableList<TeamInfo>>(
-				tournamentsettings, BigInteger.valueOf(numberTournaments),
-				true);
+		return new Repeat<ImmutableList<TeamInfo>>(tournamentsettings,
+				BigInteger.valueOf(numberTournaments), true);
 	}
 
@@ -273,6 +277,5 @@
 		BiFunction<Team, ProfileList, TeamInfo> makeparty = new BiFunction<Team, ProfileList, TeamInfo>() {
 			@Override
-			public TeamInfo apply(Team team,
-					ProfileList profilelist) {
+			public TeamInfo apply(Team team, ProfileList profilelist) {
 				List<PartyWithParameters> parties = team.getParties();
 				List<ProfileRef> profiles = profilelist.getProfiles();
@@ -291,6 +294,6 @@
 
 		};
-		return new MapThreadList<TeamInfo, Team, ProfileList>(
-				makeparty, tuple.get1(), tuple.get2());
+		return new MapThreadList<TeamInfo, Team, ProfileList>(makeparty,
+				tuple.get1(), tuple.get2());
 	}
 
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java	(revision 31)
@@ -15,8 +15,8 @@
 public class AllPermutationsState implements TournamentState {
 
-	private final AllPermutationsSettings toursettings; // original settings
-	private final ImmutableList<SessionSettings> sessionsettings;
+	protected final AllPermutationsSettings toursettings; // original settings
+	protected final ImmutableList<SessionSettings> sessionsettings;
 	// limited to MAX_INT elements. But we need to store results anyway
-	private final List<SessionResult> results;
+	protected final List<SessionResult> results;
 
 	public AllPermutationsState(AllPermutationsSettings toursettings,
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/ProfileList.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/ProfileList.java	(revision 30)
+++ 	(revision )
@@ -1,68 +1,0 @@
-package geniusweb.protocol.tournament.allpermutations;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import geniusweb.references.ProfileRef;
-
-/**
- * Contains a list of profiles, one for each {@link Team} member.
- */
-@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
-public class ProfileList {
-	@JsonValue
-	private final List<ProfileRef> profiles; // field to ensure immutability
-
-	/**
-	 * 
-	 * @param ps a list of {@link ProfileRef}s. The length and order is
-	 *           important as each will be matched with the {@link Team} member
-	 *           at the same index.
-	 */
-	@JsonCreator
-	public ProfileList(List<ProfileRef> ps) {
-		this.profiles = new ArrayList<>(ps);
-	}
-
-	public List<ProfileRef> getProfiles() {
-		return Collections.unmodifiableList(profiles);
-	}
-
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result
-				+ ((profiles == null) ? 0 : profiles.hashCode());
-		return result;
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (this == obj)
-			return true;
-		if (obj == null)
-			return false;
-		if (getClass() != obj.getClass())
-			return false;
-		ProfileList other = (ProfileList) obj;
-		if (profiles == null) {
-			if (other.profiles != null)
-				return false;
-		} else if (!profiles.equals(other.profiles))
-			return false;
-		return true;
-	}
-
-	public String toString() {
-		return profiles.toString();
-	}
-}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/Team.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/Team.java	(revision 30)
+++ 	(revision )
@@ -1,74 +1,0 @@
-package geniusweb.protocol.tournament.allpermutations;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import geniusweb.references.PartyWithParameters;
-
-/**
- * A team is a group of parties, each with their own parameters. Each will be
- * coupled to a {@link ProfileList} in the generation phase of the protocol
- *
- */
-@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
-public class Team {
-	@JsonValue
-	private final List<PartyWithParameters> parties; // ensure immutability
-
-	/**
-	 * 
-	 * @param ps a list of {@link PartyWithParameters}. Each party is always
-	 *           associated with the given parameters. The order of them is
-	 *           important as it will be matched with the order in the
-	 *           {@link ProfileList}.
-	 */
-	@JsonCreator
-	public Team(List<PartyWithParameters> ps) {
-		this.parties = new ArrayList<PartyWithParameters>(ps);
-	}
-
-	/**
-	 * 
-	 * @return list of all team {@link PartyWithParameters}.
-	 */
-	public List<PartyWithParameters> getParties() {
-		return Collections.unmodifiableList(parties);
-	}
-
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((parties == null) ? 0 : parties.hashCode());
-		return result;
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (this == obj)
-			return true;
-		if (obj == null)
-			return false;
-		if (getClass() != obj.getClass())
-			return false;
-		Team other = (Team) obj;
-		if (parties == null) {
-			if (other.parties != null)
-				return false;
-		} else if (!parties.equals(other.parties))
-			return false;
-		return true;
-	}
-
-	public String toString() {
-		return parties.toString();
-	}
-}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocol.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocol.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocol.java	(revision 31)
@@ -0,0 +1,48 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import geniusweb.protocol.tournament.TournamentState;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsProtocol;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsState;
+import geniusweb.references.ProtocolRef;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * This protocol creates sequentially all permutations of available parties and
+ * profiles, See {@link AllPermutationsLearnSettings} for details. Only 1
+ * session is run at a time because managing multiple sessions is more complex
+ * to manage in the {@link TournamentState}
+ * 
+ */
+public class AllPermutationsLearnProtocol extends AllPermutationsProtocol {
+	private static final ProtocolRef APPLearn = new ProtocolRef("APPLearn");
+
+	/**
+	 * 
+	 * @param state  the {@link AllPermutationsState}
+	 * @param logger used for all logging, about the tournament, and the created
+	 *               the session and protocols.
+	 */
+	public AllPermutationsLearnProtocol(AllPermutationsLearnState state,
+			Reporter logger) {
+		super(state, logger);
+	}
+
+	@Override
+	public String getDescription() {
+		return "Runs all possible permutations of provided parties and profiles, "
+				+ "with the given number of parties per session. Sessions run one at a time. "
+				+ "In each session, all parties receive FileLocations in the 'negotiationdata' parameter."
+				+ "If reuseParties is set, then a party can occur more than 1 time in each session. "
+				+ "The sessionSettings determine the baseline settings for each session, eg the deadline "
+				+ "and the protocol to be used. The AllPermutationsProtocol supports all basic SessionProtocols. "
+				+ "parties and protocols already available in the settings will participate in every session. "
+				+ "After all sessions have been run, a Learn session is done where all parties receive their"
+				+ "previously stored FileLocations again.";
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return APPLearn;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettings.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettings.java	(revision 31)
@@ -0,0 +1,264 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import java.math.BigInteger;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.FileLocation;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.protocol.session.learn.LearnSettings;
+import geniusweb.protocol.tournament.ProfileList;
+import geniusweb.protocol.tournament.Team;
+import geniusweb.protocol.tournament.TournamentProtocol;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsSettings;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import tudelft.utilities.immutablelist.FixedList;
+import tudelft.utilities.immutablelist.ImmutableList;
+import tudelft.utilities.immutablelist.JoinedList;
+import tudelft.utilities.immutablelist.MapThreadList;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * Almost the same as AllPermutationsSettings.
+ * <p>
+ * The only additional field is the additional learnTime. This has a maximum to
+ * the maximum number of sessions, {@link #MAX_SESSIONS}. This is because we
+ * need to store all the {@link FileLocation}s explicitly.
+ * <p>
+ * Only the first team member is actually getting the learn time.
+ * <p>
+ * We assume that all parties have the proper persistentstate parameter already.
+ * This is assumed to be set in the {@link Team} settings
+ */
+public class AllPermutationsLearnSettings extends AllPermutationsSettings {
+
+	/**
+	 * MAX_SESSIONS is the maximum number of sessions in a tournament. It is
+	 * depending on a number of factors:
+	 * <ul>
+	 * <li>The memory use to store all UUIDs for all parties
+	 * <li>The MAX_MESSAGE_SIZE of a websocket message.
+	 * </ul>
+	 * The MAX_MESSAGE_SIZE of the partyserver currently is 20MB. A single UUID
+	 * is about 45 bytes including double quotes and commas
+	 * </ul>
+	 */
+	private final static int MAX_SESSIONS = 400000;
+	private final static ObjectMapper jackson = new ObjectMapper();
+
+	private final LearnSettings learnSettings;
+
+	/**
+	 * 
+	 * @param teams           a list of {@link Team}s. Must contain at least
+	 *                        {@link #teamsPerSession} elements. The
+	 *                        {@link #teamsPerSession} must match the protocol:
+	 *                        (SAOP:1, SHAOP:2)
+	 * @param reuseTeams      if true, we use PermutationsWithReturn, if false
+	 *                        we use PermutationsWithoutReturn to create the
+	 *                        teams.
+	 * @param plists          list of available {@link ProfileList}s to be
+	 *                        permutated over the teams. Each
+	 *                        {@link ProfileList} must contain
+	 *                        {@link #teamsPerSession} elements.
+	 * @param teamsPerSession number of parties per session, must be at least 2.
+	 * @param sesettings      The generic {@link SessionSettings}.
+	 *                        {@link SessionSettings#with(TeamInfo)} will be
+	 *                        used to add the required {@link TeamInfo}s. All
+	 *                        settings must contain a 'persistentstate'
+	 *                        parameter with a {@link FileLocation}.
+	 * @param nTournaments    the number of times the tournament should be run.
+	 * @param learnSet        the {@link LearnSettings} to be used for the
+	 *                        learning phase. The participants list should be
+	 *                        empty as this will be filled up from the teams
+	 *                        provided above. Only the deadline is used.
+	 */
+	@JsonCreator
+	public AllPermutationsLearnSettings(@JsonProperty("teams") List<Team> teams,
+			@JsonProperty("profileslists") List<ProfileList> plists,
+			@JsonProperty("reuseTeams") boolean reuseTeams,
+			@JsonProperty("teamsPerSession") int teamsPerSession,
+			@JsonProperty("sessionsettings") SessionSettings sesettings,
+			@JsonProperty("numberTournaments") int nTournaments,
+			@JsonProperty("learnSettings") LearnSettings learnSet) {
+		super(teams, plists, reuseTeams, teamsPerSession, sesettings,
+				nTournaments);
+		this.learnSettings = learnSet;
+	}
+
+	@Override
+	public TournamentProtocol getProtocol(Reporter logger) {
+		AllPermutationsLearnState state = new AllPermutationsLearnState(this,
+				permutations(), Collections.emptyList());
+		return new AllPermutationsLearnProtocol(state, logger);
+	}
+
+	@Override
+	public Double getMaxRunTime() {
+		return super.permutations().size().doubleValue()
+				* (sessionsettings.getMaxRunTime() + 2d)
+				+ learnSettings.getMaxRunTime();
+	}
+
+	/**
+	 * @return {@link ImmutableList} containing all {@link SessionSettings} for
+	 *         all Sessions in the correct order.
+	 */
+	@Override
+	public ImmutableList<SessionSettings> permutations() {
+		ImmutableList<SessionSettings> allsessionsettings = super.permutations();
+		if (allsessionsettings.size()
+				.compareTo(BigInteger.valueOf(MAX_SESSIONS)) > 0)
+			throw new IllegalArgumentException(
+					"learn-tournament exceeds max number of sessions  "
+							+ MAX_SESSIONS);
+
+		FixedList<FixedList<FileLocation>> filelocs = createFileLocations(
+				allsessionsettings.size().longValue());
+
+		ImmutableList<ImmutableList<TeamInfo>> partylistlist;
+		ImmutableList<Team> partieslist = new FixedList<Team>(teams);
+		ImmutableList<ProfileList> profileslist = new FixedList<ProfileList>(
+				profileslists);
+		partylistlist = getParticipants(partieslist, profileslist,
+				teamsPerSession, reuseTeams);
+		ImmutableList<SessionSettings> settings = new MapThreadList<>(
+				(parties, flocs) -> createSetting(parties, flocs),
+				partylistlist, filelocs);
+
+		return new JoinedList<SessionSettings>(settings,
+				getLearnSessions(filelocs, partylistlist));
+	}
+
+	@Override
+	public String toString() {
+		return "AllPermutationsLearnSettings[" + contentString() + ","
+				+ learnSettings + "]";
+	}
+
+	/**
+	 * 
+	 * @param numsessions the number of sessions in the tournament
+	 * @return A list of list of {@link FileLocation}s. The outer list has one
+	 *         entry for each session, the inner list has one
+	 *         {@link FileLocation} per party.
+	 */
+	private FixedList<FixedList<FileLocation>> createFileLocations(
+			long numsessions) {
+		// like before, but all sessions need a new negotiationdata
+		List<FixedList<FileLocation>> filelocations = new ArrayList<>();
+		for (long sessionnr = 0; sessionnr < numsessions; sessionnr++) {
+			List<FileLocation> locs = new ArrayList<>();
+			for (int teamnr = 0; teamnr < teamsPerSession; teamnr++) {
+				locs.add(new FileLocation());
+			}
+			filelocations.add(new FixedList<>(locs));
+		}
+		FixedList<FixedList<FileLocation>> filelocs = new FixedList<>(
+				filelocations);
+		return filelocs;
+	}
+
+	/**
+	 * @param allsessionsettings the 'traditional session settings.
+	 * @param filelocs           the {@link FileLocation}s for each session, for
+	 *                           each party
+	 * @param partylistlist      the lists of all teams for the sessions, with
+	 *                           unaltered parameters matching the
+	 *                           {@link #teams} list
+	 * @return the final sessions, doing learning for all parties. In this
+	 *         session, the number of parties may be larger than
+	 *         {@link #teamsPerSession}. Learning may be spread over multiple
+	 *         sessions, although we don't do this currently.
+	 */
+	private ImmutableList<SessionSettings> getLearnSessions(
+			FixedList<FixedList<FileLocation>> filelocs,
+			ImmutableList<ImmutableList<TeamInfo>> partylistlist) {
+		try {
+			ProfileRef NOPROFILE = new ProfileRef("not://aprofile");
+
+			SessionSettings session = learnSettings;
+			// add all the teams, with updated parameters, to the session
+			for (Team team : teams) {
+				List<FileLocation> files = getNegoData(team, filelocs,
+						partylistlist);
+				// Tricky: correct parameter is list of UUID strings
+				List<String> filesstrings = files.stream()
+						.map(file -> file.getUUIDString())
+						.collect(Collectors.toList());
+				Parameters parameters = new Parameters().with("negotiationdata",
+						filesstrings);
+				// add all the FileLocations and NOPROFILE to all team members
+				List<PartyWithProfile> teaminfo = team.getParties().stream()
+						.map(pwp -> new PartyWithProfile(pwp.with(parameters),
+								NOPROFILE))
+						.collect(Collectors.toList());
+				session = session.with(new TeamInfo(teaminfo));
+			}
+
+			return new FixedList<SessionSettings>(Arrays.asList(session));
+		} catch (URISyntaxException e) {
+			throw new RuntimeException("Failed creating learn session", e);
+		}
+	}
+
+	/**
+	 * @param team     the team for which the {@link FileLocation}s are needed
+	 * @param filelocs the used {@link FileLocation}s for all sessions, all
+	 *                 teams.
+	 * @param TeamInfo the TeamInfo used for each session, team. Same dimensions
+	 *                 as filelocs
+	 * @return the list of FileLocations that were provided to the given team in
+	 *         the negotiation sessions.
+	 */
+	private List<FileLocation> getNegoData(Team team,
+			FixedList<FixedList<FileLocation>> filelocs,
+			ImmutableList<ImmutableList<TeamInfo>> partylistlist) {
+		List<FileLocation> locs = new ArrayList<>();
+		for (long sessionnr = 0; sessionnr < filelocs.size()
+				.longValue(); sessionnr++) {
+			for (int teamnr = 0; teamnr < teamsPerSession; teamnr++) {
+				if (team.equals(
+						partylistlist.get(sessionnr).get(teamnr).getTeam()))
+					locs.add(filelocs.get(sessionnr).get(teamnr));
+			}
+		}
+		return locs;
+
+	}
+
+	/**
+	 * 
+	 * @param partyproflist a list of {@link PartyWithProfile} settings. Now,
+	 *                      each party gets a new negotiationdata object in
+	 *                      every session.
+	 * @param locs          the FileLocations for the negotiationdata for all
+	 *                      teams
+	 * @return {@link SessionSettings}, with each party parameters extended with
+	 *         its {@link FileLocation}
+	 */
+	private SessionSettings createSetting(ImmutableList<TeamInfo> partyproflist,
+			ImmutableList<FileLocation> locs) {
+		SessionSettings settings = sessionsettings;
+		for (int teamnr = 0; teamnr < partyproflist.size()
+				.intValue(); teamnr++) {
+			Parameters extraparams = new Parameters().with("negotiationdata",
+					Arrays.asList(locs.get(teamnr)));
+			TeamInfo teaminfo = partyproflist.get(teamnr).with(extraparams);
+			settings = settings.with(teaminfo);
+		}
+		return settings;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnState.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnState.java	(revision 31)
@@ -0,0 +1,38 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsState;
+import tudelft.utilities.immutablelist.ImmutableList;
+
+/**
+ * A non-parallel simple implementation of all-permutations
+ */
+public class AllPermutationsLearnState extends AllPermutationsState {
+
+	public AllPermutationsLearnState(AllPermutationsLearnSettings toursettings,
+			ImmutableList<SessionSettings> settings,
+			List<SessionResult> results) {
+		super(toursettings, settings, results);
+	}
+
+	/**
+	 * 
+	 * @param result the next {@link SessionResult}
+	 * @return new state
+	 * @throws IllegalArgumentException if the result participants are not
+	 *                                  matching the expected participants
+	 */
+	@Override
+	public AllPermutationsLearnState with(SessionResult result) {
+		ArrayList<SessionResult> newresults = new ArrayList<>(results);
+		newresults.add(result);
+		return new AllPermutationsLearnState(
+				(AllPermutationsLearnSettings) toursettings, sessionsettings,
+				newresults);
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/DefaultSessionStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/DefaultSessionStateTest.java	(revision 30)
+++ 	(revision )
@@ -1,151 +1,0 @@
-package geniusweb.protocol.session;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import geniusweb.actions.Action;
-import geniusweb.actions.EndNegotiation;
-import geniusweb.actions.PartyId;
-import geniusweb.deadline.DeadlineTime;
-import geniusweb.inform.Agreements;
-import geniusweb.progress.Progress;
-import geniusweb.progress.ProgressTime;
-import geniusweb.protocol.ProtocolException;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
-import geniusweb.protocol.session.saop.SAOPSettings;
-import geniusweb.references.Parameters;
-import geniusweb.references.PartyRef;
-import geniusweb.references.PartyWithParameters;
-import geniusweb.references.PartyWithProfile;
-import geniusweb.references.ProfileRef;
-import tudelft.utilities.junit.GeneralTests;
-
-public class DefaultSessionStateTest extends GeneralTests<DefaultSessionState> {
-	private final ObjectMapper jackson = new ObjectMapper();
-	private DefaultSessionState1 state1, state1a, stateA, stateE, stateF;
-	// The 1 is because we are testing an instance, not the abstract class
-	private String state1string = "{\"DefaultSessionState1\":{\"actions\":[],\"progress\":{\"time\":{\"duration\":1000,\"start\":1000}},\"settings\":{\"SAOPSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}]}},{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party2\",\"parameters\":{}},\"profile\":\"profile2\"}]}}],\"deadline\":{\"deadlinetime\":{\"durationms\":1000}}}},\"error\":null,\"partyprofiles\":{\"party1\":{\"party\":{\"partyref\":\"party1\",\"parameters\":{}},\"profile\":\"profile1\"}}}}";
-	private ProgressTime progr;
-	private final Date start = new Date(1000l);
-
-	@Before
-	public void before() throws URISyntaxException {
-		List<Action> actions = new LinkedList<>();
-		PartyId party1 = new PartyId("party1");
-		actions.add(new EndNegotiation(party1));
-		ProtocolToPartyConnections conns = new ProtocolToPartyConnections(
-				Collections.EMPTY_LIST);
-		DeadlineTime deadline = new DeadlineTime(1000);
-		progr = new ProgressTime(deadline.getDuration(), start);
-		PartyWithParameters party1ref = new PartyWithParameters(
-				new PartyRef("party1"), new Parameters());
-		ProfileRef profile1 = new ProfileRef("profile1");
-		PartyWithParameters party2ref = new PartyWithParameters(
-				new PartyRef("party2"), new Parameters());
-		ProfileRef profile2 = new ProfileRef("profile2");
-		List<TeamInfo> participants = Arrays.asList(
-				new TeamInfo(party1ref, profile1),
-				new TeamInfo(party2ref, profile2));
-		SessionSettings settings = new SAOPSettings(participants, deadline);
-		ProtocolException e = new ProtocolException("test protocol error",
-				party1);
-		HashMap<PartyId, PartyWithProfile> partyprofiles = new HashMap<PartyId, PartyWithProfile>();
-		partyprofiles.put(party1, new PartyWithProfile(party1ref, profile1));
-		state1 = new DefaultSessionState1(Collections.EMPTY_LIST, null, progr,
-				settings, partyprofiles, null);
-		state1a = new DefaultSessionState1(Collections.EMPTY_LIST, null, progr,
-				settings, partyprofiles, null);
-
-		stateA = new DefaultSessionState1(actions, conns, progr, settings,
-				partyprofiles, null);
-
-		stateE = new DefaultSessionState1(Collections.EMPTY_LIST, conns, progr,
-				settings, partyprofiles, e);
-		stateF = new DefaultSessionState1(Collections.EMPTY_LIST, conns, progr,
-				settings, null, e);
-
-	}
-
-	@Override
-	public List<List<DefaultSessionState>> getGeneralTestData() {
-
-		return Arrays.asList(Arrays.asList(state1, state1a),
-				Arrays.asList(stateA), Arrays.asList(stateE),
-				Arrays.asList(stateF));
-	}
-
-	@Override
-	public List<String> getGeneralTestStrings() {
-		return Arrays.asList(
-				"DefaultSessionState1.*\\[\\].*ProgressTime.*1000ms.*SAOPSettings.*party1.*party2.*,null]",
-				"DefaultSessionState1.*\\[.*EndNegotiation.*party1.*\\].*,.*,ProgressTime.*1000ms.*SAOPSettings.*party1.*party2.*,null]",
-				"DefaultSessionState1.*\\[\\],.*,ProgressTime.*1000ms.*,SAOPSettings.*party1.*party2.*,.*ProtocolException: party1:test protocol error.*",
-				"DefaultSessionState1.*\\[\\],.*,ProgressTime.*1000ms.*,SAOPSettings.*party1.*party2.*,.*ProtocolException: party1:test protocol error.*");
-	}
-
-	@Test
-	public void serializeTest() throws JsonProcessingException {
-		System.out.println(jackson.writeValueAsString(state1));
-		assertEquals(state1string, jackson.writeValueAsString(state1));
-	}
-
-	@Test
-	public void deserializeTest() throws IOException {
-		// note, we don't use DefaultSessionState because we can't add the
-		// annotation nor tell jackson dynamically about the test class.
-		DefaultSessionState state = jackson.readValue(state1string,
-				DefaultSessionState1.class);
-		// compare fields, as sessionended is a derived/new inner class
-		assertEquals(progr, state.getProgress());
-	}
-}
-
-/**
- * We need to implement a few things because we can not instantiate an abstract
- * class.
- */
-class DefaultSessionState1 extends DefaultSessionState {
-
-	private DefaultSessionState1() {
-		super();
-	}
-
-	public DefaultSessionState1(List<Action> actions,
-			ProtocolToPartyConnections conns, Progress progr,
-			SessionSettings settings,
-			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
-		super(actions, conns, progr, settings, partyprofiles, e);
-	}
-
-	@Override
-	public SessionResult getResult() {
-
-		Map<PartyId, Double> emptymap = getPartyProfiles().keySet().stream()
-				.collect(Collectors.toMap(pid -> pid, pid -> 0d));
-		return new SessionResult(getPartyProfiles(), getAgreements(), emptymap,
-				getError());
-
-	}
-
-	@Override
-	public Agreements getAgreements() {
-		return new Agreements();
-	}
-
-}
Index: protocol/src/test/java/geniusweb/protocol/session/learn/LearnSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/learn/LearnSettingsTest.java	(revision 31)
+++ protocol/src/test/java/geniusweb/protocol/session/learn/LearnSettingsTest.java	(revision 31)
@@ -0,0 +1,222 @@
+package geniusweb.protocol.session.learn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.deadline.Deadline;
+import geniusweb.deadline.DeadlineRounds;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyRef;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import tudelft.utilities.junit.GeneralTests;
+import tudelft.utilities.logging.Reporter;
+
+public class LearnSettingsTest extends GeneralTests<LearnSettings> {
+	private final ObjectMapper jackson = new ObjectMapper();
+	LearnSettings settings1, settings1a, settings2, settings3;
+	private DeadlineRounds deadline;
+	private List<TeamInfo> participants, participants2;
+	private Parameters params;
+
+	private final String serialized = "{\"LearnSettings\":{\"participants\":[{\"TeamInfo\":{\"parties\":[{\"party\":{\"partyref\":\"party1\",\"parameters\":{\"persistentstate\":\"6bb5f909-0079-43ac-a8ac-a31794391074\",\"negotiationdata\":[\"12b5f909-0079-43ac-a8ac-a31794391012\"]}},\"profile\":\"http://prof1\"}]}}],\"deadline\":{\"deadlinerounds\":{\"rounds\":10,\"durationms\":10000}}}}";
+
+	@Before
+	public void before() throws URISyntaxException, JsonProcessingException {
+
+		params = new Parameters();
+		params = params.with("persistentstate",
+				"6bb5f909-0079-43ac-a8ac-a31794391074");
+		params = params.with("negotiationdata",
+				Arrays.asList(("12b5f909-0079-43ac-a8ac-a31794391012")));
+
+		// define participants
+		participants = createTeam(params, 1);
+
+		// participants2
+//		ProfileRef profile2 = new ProfileRef("http://prof2");
+//		PartyWithProfile partywithp2 = new PartyWithProfile(party1, profile2);
+//		List<PartyWithProfile> team2pp = Arrays.asList(partywithp2);
+//		TeamInfo team2 = new TeamInfo(team2pp);
+		participants2 = createTeam(params, 2);
+
+		deadline = new DeadlineRounds(10, 10000);
+		Deadline deadline2 = new DeadlineRounds(20, 10000);
+
+		settings1 = new LearnSettings(participants, deadline);
+		settings1a = new LearnSettings(participants, deadline);
+		settings2 = new LearnSettings(participants2, deadline);
+		settings3 = new LearnSettings(participants, deadline2);
+
+	}
+
+	/**
+	 * 
+	 * @param params  the parameters for the party
+	 * @param partynr the serialnr for the party, eg 1 or 2.
+	 * @return a List of TeamInfo with just 1 team.
+	 */
+	private List<TeamInfo> createTeam(Parameters params, int partynr)
+			throws URISyntaxException {
+		PartyRef party1ref = new PartyRef("party" + partynr);
+		PartyWithParameters party1 = new PartyWithParameters(party1ref, params);
+		ProfileRef profile1 = new ProfileRef("http://prof" + partynr);
+		PartyWithProfile partywithp1 = new PartyWithProfile(party1, profile1);
+		List<PartyWithProfile> team1pp = Arrays.asList(partywithp1);
+		TeamInfo team1 = new TeamInfo(team1pp);
+		return Arrays.asList(team1);
+
+	}
+
+	@Override
+	public List<List<LearnSettings>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(settings1, settings1a),
+				Arrays.asList(settings2), Arrays.asList(settings3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"LearnSettings.TeamInfo.*PartyWithProfile.*PartyRef.*party1.*persistentstate=6b.*, negotiationdata=\\[12.*..*ProfileRef.*prof1.*,DeadlineRounds.*10,10000.*",
+				"LearnSettings.TeamInfo.*PartyWithProfile.*PartyRef.*party2.*persistentstate=6b.*, negotiationdata=\\[12.*..*ProfileRef.*prof2.*,DeadlineRounds.*10,10000.*",
+				"LearnSettings.TeamInfo.*PartyWithProfile.*PartyRef.*party1.*persistentstate=6b.*, negotiationdata=\\[12.*..*ProfileRef.*prof1.*,DeadlineRounds.*20,10000.*");
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testNullParticipants() {
+		new LearnSettings(null, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testNullDeadline() {
+		new LearnSettings(participants, null);
+	}
+
+	@Test
+	public void getMaxRuntimeTest() {
+		assertEquals(10d, settings1.getMaxRunTime(), 0.00000001);
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void getProtocolTest() {
+		// this will throw if not Learn protocol or another problem.
+		Learn protocol = (Learn) settings1.getProtocol(mock(Reporter.class));
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void badPersistentTest() throws URISyntaxException {
+		// hack. #1933.
+		Parameters newparams = params.with("persistentstate",
+				"\"notproperUUID\"");
+		participants = createTeam(newparams, 1);
+
+		new LearnSettings(participants, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void persistentNotStringTest() throws URISyntaxException {
+		Parameters newparams = params.with("persistentstate", 32);
+		participants = createTeam(newparams, 1);
+
+		new LearnSettings(participants, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void persistentWithoutQuotesTest() throws URISyntaxException {
+		Parameters newparams = params.with("persistentstate",
+				"6bb5f909-0079-43ac-a8ac-a31794391074");
+		participants = createTeam(newparams, 1);
+
+		new LearnSettings(participants, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void persistentExtraQuoteTest() throws URISyntaxException {
+		Parameters newparams = params.with("persistentstate",
+				"\"6bb5f909-0079-43ac-a8ac-a31794391074\"");
+		participants = createTeam(newparams, 1);
+
+		new LearnSettings(participants, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void negotiationDataNotList() throws URISyntaxException {
+		Parameters newparams = params.with("negotiationdata",
+				"\"12b5f909-0079-43ac-a8ac-a31794391012\"");
+		participants = createTeam(newparams, 1);
+		new LearnSettings(participants, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void negotiationDataBadContents() throws URISyntaxException {
+		Parameters newparams = params.with("negotiationdata", "\"bad\"");
+		participants = createTeam(newparams, 1);
+		new LearnSettings(participants, deadline);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void negotiationDataNotString() throws URISyntaxException {
+		Parameters newparams = params.with("negotiationdata", 12);
+		participants = createTeam(newparams, 1);
+		new LearnSettings(participants, deadline);
+	}
+
+	@Test
+	public void getTeamsTest() {
+		assertEquals(participants, settings1.getTeams());
+		assertEquals((Integer) 1, settings1.getTeamSize());
+	}
+
+	@Test
+	public void getDeadlineTest() {
+		assertEquals(deadline, settings1.getDeadline());
+	}
+
+	@Test
+	public void withTeamTest() {
+		SessionSettings newset = settings1.with(participants2.get(0));
+		assertEquals(2, newset.getTeams().size());
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+		SessionSettings obj = jackson.readValue(serialized,
+				SessionSettings.class);
+		System.out.println(obj);
+		assertEquals(settings1, obj);
+	}
+
+	@Test
+	public void testSerialize()
+			throws JsonProcessingException, URISyntaxException {
+
+		String string = jackson.writeValueAsString(settings1);
+		System.out.println(string);
+		assertEquals(serialized, string);
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/learn/LearnStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/learn/LearnStateTest.java	(revision 31)
+++ protocol/src/test/java/geniusweb/protocol/session/learn/LearnStateTest.java	(revision 31)
@@ -0,0 +1,162 @@
+package geniusweb.protocol.session.learn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.LearningDone;
+import geniusweb.actions.PartyId;
+import geniusweb.deadline.Deadline;
+import geniusweb.deadline.DeadlineTime;
+import geniusweb.inform.Agreements;
+import geniusweb.progress.Progress;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyRef;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+
+public class LearnStateTest {
+
+	private static final String PARTY2 = "party2";
+	private static final String PARTY1 = "party1";
+	private static final PartyId party1id = new PartyId(PARTY1);
+	private static final PartyId party2id = new PartyId(PARTY2);
+	private LearnState state;
+	private final Deadline deadline = new DeadlineTime(10000);
+	private Parameters params;
+	private PartyWithProfile partyprofile1 = mock(PartyWithProfile.class);
+	private PartyWithProfile partyprofile2 = mock(PartyWithProfile.class);
+	private LearnState connectedstate;
+	private final Progress progressnotdone = mock(Progress.class);
+	private final Progress progressdone = mock(Progress.class);
+
+	@Before
+	public void before() throws URISyntaxException {
+		TeamInfo team1, team2;
+		params = new Parameters();
+		params = params.with("persistentstate",
+				"6bb5f909-0079-43ac-a8ac-a31794391074");
+		params = params.with("negotiationdata",
+				Arrays.asList("12b5f909-0079-43ac-a8ac-a31794391012"));
+
+		when(progressnotdone.isPastDeadline(any())).thenReturn(false);
+		when(progressdone.isPastDeadline(any())).thenReturn(true);
+
+		// define team1
+		PartyRef party1ref = new PartyRef(PARTY1);
+		PartyWithParameters party1 = new PartyWithParameters(party1ref, params);
+		ProfileRef profile1 = new ProfileRef("http://prof1");
+		PartyWithProfile partywithp1 = new PartyWithProfile(party1, profile1);
+		List<PartyWithProfile> team1pp = Arrays.asList(partywithp1);
+		team1 = new TeamInfo(team1pp);
+
+		// define team2
+		PartyRef party2ref = new PartyRef(PARTY2);
+		PartyWithParameters party2 = new PartyWithParameters(party1ref, params);
+		ProfileRef profile2 = new ProfileRef("http://prof2");
+		PartyWithProfile partywithp2 = new PartyWithProfile(party2, profile2);
+		List<PartyWithProfile> team2pp = Arrays.asList(partywithp2);
+		team2 = new TeamInfo(team1pp);
+
+		List<TeamInfo> participants = Arrays.asList(team1, team2);
+		LearnSettings settings = new LearnSettings(participants, deadline);
+		this.state = new LearnState(settings);
+
+		this.connectedstate = state.with(party1id, partyprofile1)
+				.with(party2id, partyprofile2).with(progressnotdone);
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void smokeTestNull() {
+		new LearnState(null);
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void smokeTest() {
+		LearnSettings settings = mock(LearnSettings.class);
+		new LearnState(settings);
+	}
+
+	@Test
+	public void getAgreementsTest() {
+		assertEquals(new Agreements(), state.getAgreements());
+	}
+
+	@Test
+	public void isFinalTest() {
+		// initial state is false, because we check
+		// that there are no learningDone reports
+		assertFalse(state.isFinal(0));
+		// not final because there are connected parties not yet done.
+		assertFalse(connectedstate.isFinal(0));
+		LearnState finalstate = connectedstate.with(progressdone);
+		assertTrue(finalstate.isFinal(0));
+
+		LearnState errorstate = connectedstate
+				.with(new ProtocolException("test", party1id));
+		assertTrue(errorstate.isFinal(0));
+	}
+
+	@Test
+	public void checkActionTest() {
+		// check basic illegal actions in Learning
+		assertNotNull(connectedstate.checkAction(party1id, null));
+		assertNotNull(connectedstate.checkAction(new PartyId("unknown"), null));
+		assertNotNull(connectedstate.checkAction(party1id,
+				new EndNegotiation(party1id)));
+		assertNotNull(connectedstate.checkAction(party1id,
+				new LearningDone(party2id)));
+
+		// check a good action is allowed
+		assertNull(connectedstate.checkAction(party1id,
+				new LearningDone(party1id)));
+
+		// no learning twice not ok
+		LearnState learned = connectedstate.with(party1id,
+				new LearningDone(party1id));
+		assertNotNull(
+				learned.checkAction(party1id, new LearningDone(party1id)));
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void LearnDoneActionTest() {
+		LearnState learned = connectedstate.with(party1id,
+				new LearningDone(party1id));
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void isFinalWhenAllLearned() {
+		LearnState learned = connectedstate
+				.with(party1id, new LearningDone(party1id))
+				.with(party2id, new LearningDone(party2id));
+		assertTrue(learned.isFinal(0));
+	}
+
+	@Test
+	public void getResultTest() {
+		SessionResult res = connectedstate.getResult();
+		assertTrue(res.getAgreements().getMap().isEmpty());
+	}
+}
Index: protocol/src/test/java/geniusweb/protocol/session/learn/LearnTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/learn/LearnTest.java	(revision 31)
+++ protocol/src/test/java/geniusweb/protocol/session/learn/LearnTest.java	(revision 31)
@@ -0,0 +1,177 @@
+package geniusweb.protocol.session.learn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.LearningDone;
+import geniusweb.actions.PartyId;
+import geniusweb.deadline.Deadline;
+import geniusweb.deadline.DeadlineTime;
+import geniusweb.inform.Finished;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyRef;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import tudelft.utilities.logging.Reporter;
+import tudelft.utilities.repository.NoResourcesNowException;
+
+public class LearnTest {
+
+	private final Reporter reporter = mock(Reporter.class);
+	private Parameters params = new Parameters();
+	private Deadline deadline = new DeadlineTime(1800);
+	private PartyId party1id = new PartyId("party1");;
+	private PartyId party2id = new PartyId("party2");;
+	private final ProtocolToPartyConn conn1 = mock(ProtocolToPartyConn.class),
+			conn2 = mock(ProtocolToPartyConn.class);
+
+	@Before
+	public void before() {
+		params = params.with("persistentstate",
+				"6bb5f909-0079-43ac-a8ac-a31794391074");
+		params = params.with("negotiationdata",
+				Arrays.asList("12b5f909-0079-43ac-a8ac-a31794391012"));
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void smokeTest() {
+		new Learn(mock(LearnState.class), mock(Reporter.class));
+	}
+
+	@Test
+	public void getDescrTest() {
+		Learn l = new Learn(mock(LearnState.class), mock(Reporter.class));
+
+		assertNotNull(l.getDescription());
+	}
+
+	@Test
+	public void getStateTest() {
+		LearnState state = mock(LearnState.class);
+		Learn l = new Learn(state, mock(Reporter.class));
+		assertEquals(state, l.getState());
+	}
+
+	@Test
+	public void getRefTest() {
+		Learn l = new Learn(mock(LearnState.class), mock(Reporter.class));
+		assertEquals("Learn", l.getRef().getURI().getPath().toString());
+	}
+
+	@Test
+	public void finalStateNotificationTest() {
+		// check that listeners get notified when session ends.
+
+	}
+
+	@Test
+	public void testStartStopBasic() throws IOException,
+			NoResourcesNowException, InterruptedException, URISyntaxException {
+		Learn learn = createBasicLearn();
+		ProtocolToPartyConnFactory factory = createFactory();
+		learn.start(factory);
+		assertFalse(learn.getState().isFinal(System.currentTimeMillis()));
+		Thread.sleep(2000);
+		assertTrue(learn.getState().isFinal(System.currentTimeMillis()));
+	}
+
+	@Test
+	public void testStartStopLearn() throws IOException,
+			NoResourcesNowException, InterruptedException, URISyntaxException {
+		Learn learn = createBasicLearn();
+		ProtocolToPartyConnFactory factory = createFactory();
+		learn.start(factory);
+		assertFalse(learn.getState().isFinal(System.currentTimeMillis()));
+
+		// Instead of mocking connectin we call actionRequest directly
+		learn.actionRequest(conn1, new LearningDone(party1id));
+		assertFalse(learn.getState().isFinal(System.currentTimeMillis()));
+		learn.actionRequest(conn2, new LearningDone(party2id));
+		assertTrue(learn.getState().isFinal(System.currentTimeMillis()));
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testAddParty() {
+		Learn l = new Learn(mock(LearnState.class), mock(Reporter.class));
+		l.addParticipant(mock(PartyWithProfile.class));
+	}
+
+	@Test
+	public void isFinishSentNormally()
+			throws URISyntaxException, IOException, NoResourcesNowException {
+		Learn learn = createBasicLearn();
+		ProtocolToPartyConnFactory factory = createFactory();
+		learn.start(factory);
+		learn.actionRequest(conn1, new LearningDone(party1id));
+		learn.actionRequest(conn2, new LearningDone(party2id));
+		verify(conn1, times(1)).send(isA(Finished.class));
+		verify(conn2, times(1)).send(isA(Finished.class));
+	}
+
+	@Test
+	public void isFinishSentInError()
+			throws URISyntaxException, IOException, NoResourcesNowException {
+		Learn learn = createBasicLearn();
+		ProtocolToPartyConnFactory factory = createFactory();
+		learn.start(factory);
+		learn.actionRequest(conn1, new EndNegotiation(party1id));
+		verify(conn1, times(1)).send(isA(Finished.class));
+		verify(conn2, times(1)).send(isA(Finished.class));
+	}
+
+	private Learn createBasicLearn() throws URISyntaxException {
+		TeamInfo team1 = createTeam(1);
+		TeamInfo team2 = createTeam(2);
+		LearnSettings settings = new LearnSettings(Arrays.asList(team1, team2),
+				deadline);
+		return (Learn) settings.getProtocol(reporter);
+
+	}
+
+	private TeamInfo createTeam(int nr) throws URISyntaxException {
+		PartyRef party1ref = new PartyRef("party" + nr);
+		PartyWithParameters party1 = new PartyWithParameters(party1ref, params);
+		ProfileRef profile1 = new ProfileRef("prof" + nr);
+		PartyWithProfile partywithp1 = new PartyWithProfile(party1, profile1);
+		List<PartyWithProfile> team1pp = Arrays.asList(partywithp1);
+		TeamInfo team = new TeamInfo(team1pp);
+		return team;
+	}
+
+	private ProtocolToPartyConnFactory createFactory()
+			throws IOException, NoResourcesNowException {
+		when(conn1.getParty()).thenReturn(party1id);
+		when(conn2.getParty()).thenReturn(party2id);
+
+		ProtocolToPartyConnFactory factory = mock(
+				ProtocolToPartyConnFactory.class);
+		// connections = mock(List.class);
+		List<ProtocolToPartyConn> connections = Arrays.asList(conn1, conn2);
+		when(factory.connect(any(List.class))).thenReturn(connections);
+		return factory;
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACStateTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac/MOPACStateTest.java	(revision 31)
@@ -79,5 +79,5 @@
 	public void before()
 			throws JsonParseException, JsonMappingException, IOException {
-		params1.put("power", 2);
+		params1 = params1.with("power", 2);
 		when(partyparams1.getParameters()).thenReturn(params1);
 
Index: protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2StateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2StateTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/mopac2/MOPAC2StateTest.java	(revision 31)
@@ -83,5 +83,5 @@
 	public void before()
 			throws JsonParseException, JsonMappingException, IOException {
-		params1.put("power", 2);
+		params1 = params1.with("power", 2);
 		when(partyparams1.getParameters()).thenReturn(params1);
 
Index: protocol/src/test/java/geniusweb/protocol/session/saop/SAOPStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/saop/SAOPStateTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/saop/SAOPStateTest.java	(revision 31)
@@ -28,6 +28,5 @@
 import geniusweb.protocol.ProtocolException;
 import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
-import geniusweb.protocol.session.SessionSettings;
+import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.junit.GeneralTests;
 
@@ -46,15 +45,12 @@
 	private ProtocolToPartyConn party3conn = mock(ProtocolToPartyConn.class);
 
-	private ProtocolToPartyConnections connections = new ProtocolToPartyConnections(
-			Arrays.asList(party1conn, party2conn));
-	private ProtocolToPartyConnections connections2 = new ProtocolToPartyConnections(
-			Arrays.asList(party1conn, party3conn));
-	private ProtocolToPartyConnections connections3 = new ProtocolToPartyConnections(
-			Arrays.asList(party1conn, party2conn, party3conn));
+	private List<PartyId> connections = Arrays.asList(party1, party2);
+	private List<PartyId> connections2 = Arrays.asList(party1, party3);
+	private List<PartyId> connections3 = Arrays.asList(party1, party2, party3);
 
 	private Progress progresstime = mock(ProgressTime.class);
 	private ProgressRounds progressrounds = mock(ProgressRounds.class);
 	private ProgressRounds progressrounds1 = mock(ProgressRounds.class);
-	private SessionSettings settings = mock(SAOPSettings.class);
+	private SAOPSettings settings = mock(SAOPSettings.class);
 	private SAOPState state1, state1a, state2, state3, state4;
 
@@ -106,14 +102,14 @@
 	public List<String> getGeneralTestStrings() {
 		return Arrays.asList(
-				"SAOPState.*\\[\\].*ConnectionWithParties.*conn1.*conn2.*ProgressTime.*",
-				"SAOPState.*\\[act1\\].*ConnectionWithParties.*conn1.*conn2.*ProgressTime.*",
-				"SAOPState.*ConnectionWithParties.*conn1.*conn3.*ProgressTime.*",
-				"SAOPState.*ConnectionWithParties.*conn1.*conn2.*ProgressRounds.*");
+				"SAOPState.*\\[\\].*party1.*party2.*ProgressTime.*",
+				"SAOPState.*\\[act1\\].*party1.*party2.*ProgressTime.*",
+				"SAOPState.*party1.*party3.*ProgressTime.*",
+				"SAOPState.*party1.*party2.*ProgressRounds.*");
 	}
 
 	@Test
 	public void constructWith0Connection() {
-		new SAOPState(actions, new ProtocolToPartyConnections(Arrays.asList()),
-				progresstime, settings, null, null);
+		new SAOPState(actions, Arrays.asList(), progresstime, settings, null,
+				null);
 	}
 
@@ -132,6 +128,5 @@
 	@Test
 	public void testNextActor2() {
-		ProtocolToPartyConnections conns = new ProtocolToPartyConnections(
-				Arrays.asList(party2conn, party3conn));
+		List<PartyId> conns = Arrays.asList(party2, party3);
 		SAOPState state = new SAOPState(actions, conns, progresstime, settings,
 				null, null);
@@ -237,5 +232,5 @@
 	@Test
 	public void withConnectionTest() {
-		SAOPState state = state1.with(party3conn, null);
+		SAOPState state = state1.with(party3, (PartyWithProfile) null);
 		assertEquals(3, state.getConnections().size());
 	}
@@ -274,8 +269,4 @@
 
 		state.with(party1, offer1);
-//		assertTrue(state.isFinal(NOW));
-//		assertEquals(
-//				"geniusweb.protocol.ProtocolException: party1:Party does not have the turn ",
-//				state.getError().toString());
 	}
 
@@ -297,5 +288,5 @@
 				settings, null, null);
 
-		state = state.with(party2, null);
+		state = state.with(party2, (Action) null);
 	}
 
Index: protocol/src/test/java/geniusweb/protocol/session/saop/SAOPTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/saop/SAOPTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/saop/SAOPTest.java	(revision 31)
@@ -6,4 +6,5 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -34,7 +35,7 @@
 import geniusweb.events.CurrentState;
 import geniusweb.events.ProtocolEvent;
+import geniusweb.inform.ActionDone;
 import geniusweb.inform.Agreements;
 import geniusweb.inform.Finished;
-import geniusweb.inform.Inform;
 import geniusweb.inform.Settings;
 import geniusweb.inform.YourTurn;
@@ -80,5 +81,5 @@
 	private SAOP saop;
 	private ProtocolToPartyConnFactory factory;
-	private List connections;
+	private List<ProtocolToPartyConn> connections;
 	private ProtocolToPartyConn conn1 = mock(ProtocolToPartyConn.class),
 			conn2 = mock(ProtocolToPartyConn.class);
@@ -87,6 +88,7 @@
 	private ProfileRef profile1;
 	private ProfileRef profile2;
-	private ProtocolToPartyConnections connectionswithparties = mock(
-			ProtocolToPartyConnections.class);;
+	private ProtocolToPartyConnections connectionswithparties;// = new
+																// ProtocolTmock(
+																// ProtocolToPartyConnections.class);;
 	@SuppressWarnings("unchecked")
 	private final Listener<ProtocolEvent> testlistener = mock(Listener.class);
@@ -128,5 +130,6 @@
 
 		factory = mock(ProtocolToPartyConnFactory.class);
-		connections = mock(List.class);
+		// connections = mock(List.class);
+		connections = Arrays.asList(conn1, conn2);
 		when(factory.connect(any(List.class))).thenReturn(connections);
 
@@ -143,18 +146,22 @@
 		when(failstate.isFinal(anyLong())).thenReturn(true);
 
-		when(connectionswithparties.get(eq(PARTY1ID))).thenReturn(conn1);
-		when(connectionswithparties.get(eq(PARTY2ID))).thenReturn(conn2);
+		connectionswithparties = new ProtocolToPartyConnections(
+				Arrays.asList(conn1, conn2));
+
+//		when(connectionswithparties.get(eq(PARTY1ID))).thenReturn(conn1);
+//		when(connectionswithparties.get(eq(PARTY2ID))).thenReturn(conn2);
 		// HACK thenReturn twice, so that 2nd call to iterator() returns new
 		// iterator instead of the old one
-		when(connectionswithparties.iterator())
-				.thenReturn(Arrays.asList(conn1, conn2).iterator())
-				.thenReturn(Arrays.asList(conn1, conn2).iterator());
-		saop = new SAOP(state, new ReportToLogger("test"));
-		saop.addListener(testlistener);
-
+//		when(connectionswithparties.iterator())
+//				.thenReturn(Arrays.asList(conn1, conn2).iterator())
+//				.thenReturn(Arrays.asList(conn1, conn2).iterator());
 		Map<PartyId, PartyWithProfile> profilesmap = new HashMap<>();
 		profilesmap.put(PARTY1ID, pwp1);
 		profilesmap.put(PARTY2ID, pwp2);
 		when(state.getPartyProfiles()).thenReturn(profilesmap);
+
+		saop = new SAOP(state, new ReportToLogger("test"),
+				connectionswithparties);
+		saop.addListener(testlistener);
 
 	}
@@ -168,9 +175,13 @@
 	private void mockState(SAOPState state, String asText) {
 		when(state.getSettings()).thenReturn(settings);
-		when(state.getConnections()).thenReturn(connectionswithparties);
+		when(state.getConnections())
+				.thenReturn(Arrays.asList(PARTY1ID, PARTY2ID));
+		// when(state.getConnections()).thenReturn(connectionswithparties);
 
 		when(state.getProgress()).thenReturn(progress);
-		when(state.with(any(ProtocolToPartyConn.class),
-				any(PartyWithProfile.class))).thenReturn(connectedstate);
+		when(state.with(any(PartyId.class), any(PartyWithProfile.class)))
+				.thenReturn(connectedstate);
+//		when(state.with(any(ProtocolToPartyConn.class),
+//				any(PartyWithProfile.class))).thenReturn(connectedstate);
 		when(state.getNextActor()).thenReturn(PARTY1ID);
 		when(state.with(any(PartyId.class), any(EndNegotiation.class)))
@@ -192,5 +203,5 @@
 		when(set.getDeadline()).thenReturn(dl);
 		when(dl.getDuration()).thenReturn(1000l);
-		new SAOP(state1, new ReportToLogger("test"));
+		new SAOP(state1, new ReportToLogger("test"), connectionswithparties);
 		verify(state1, times(0)).with(any(ProtocolException.class));
 		verify(testlistener, times(0)).notifyChange(any(CurrentState.class));
@@ -281,14 +292,16 @@
 		verify(state, times(0)).with(any(ProtocolException.class));
 		verify(state, times(1)).with(eq(PARTY1ID), any(EndNegotiation.class));
-		verify(connectionswithparties, times(1)).broadcast(any(Inform.class));
+		// verify broadcast
+		verify(conn1, times(1)).send(isA(ActionDone.class));
+		verify(conn2, times(1)).send(isA(ActionDone.class));
 
 		// state.getNextActor() is frozen to PARTY1 by our mocking
-		verify(conn1, times(1)).send(any(YourTurn.class));
-		verify(conn2, times(0)).send(any(YourTurn.class));
-		verify(testlistener, times(0)).notifyChange(any(CurrentState.class));
-	}
-
-	@Test
-	public void testFinalActionRequest() throws IOException {
+		verify(conn1, times(1)).send(isA(YourTurn.class));
+		verify(conn2, times(0)).send(isA(YourTurn.class));
+		verify(testlistener, times(0)).notifyChange(any(CurrentState.class));
+	}
+
+	@Test
+	public void testFinalActionRequest() throws IOException, ProtocolException {
 		when(state.getNextActor()).thenReturn(PARTY1ID);
 		when(state.with(any(PartyId.class), any(EndNegotiation.class)))
@@ -296,13 +309,16 @@
 		// when(finalstate.getAgreements()).thenReturn(mock(Agreements.class));
 
+		saop.nextTurn(); // ensure we send YourTurn to party1.
 		saop.actionRequest(conn1, mock(EndNegotiation.class));
 
 		verify(state, times(0)).with(any(ProtocolException.class));
 		verify(state, times(1)).with(eq(PARTY1ID), any(EndNegotiation.class));
-		verify(connectionswithparties, times(1)).broadcast(any(Finished.class));
+		// verify broadcast
+		verify(conn1, times(1)).send(isA(Finished.class));
+		verify(conn2, times(1)).send(isA(Finished.class));
 		verify(testlistener, times(1)).notifyChange(any(CurrentState.class));
 
-		verify(conn1, times(1)).send(any(YourTurn.class));
-		verify(conn2, times(1)).send(any(YourTurn.class));
+		verify(conn1, times(1)).send(isA(YourTurn.class));
+		verify(conn2, times(0)).send(isA(YourTurn.class));
 	}
 
@@ -339,5 +355,6 @@
 	@Test
 	public void testActionInFinalState() throws IOException {
-		saop = new SAOP(finalstate, new ReportToLogger("test"));
+		saop = new SAOP(finalstate, new ReportToLogger("test"),
+				connectionswithparties);
 		// when(finalstate.getAgreements()).thenReturn(mock(Agreements.class));
 
Index: protocol/src/test/java/geniusweb/protocol/session/saop/SaopPartyWithProfileTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/session/saop/SaopPartyWithProfileTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/session/saop/SaopPartyWithProfileTest.java	(revision 31)
@@ -43,6 +43,5 @@
 
 		Parameters settings1 = new Parameters();
-		Parameters settings2 = new Parameters();
-		settings2.put("a", 1);
+		Parameters settings2 = new Parameters().with("a", 1);
 
 		PartyWithParameters party1withparams1 = new PartyWithParameters(party1,
Index: protocol/src/test/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettingsTest.java	(revision 30)
+++ protocol/src/test/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettingsTest.java	(revision 31)
@@ -19,4 +19,6 @@
 import geniusweb.protocol.NegoSettings;
 import geniusweb.protocol.session.saop.SAOPSettings;
+import geniusweb.protocol.tournament.ProfileList;
+import geniusweb.protocol.tournament.Team;
 import geniusweb.protocol.tournament.TournamentSettings;
 import geniusweb.references.Parameters;
Index: protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocolTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocolTest.java	(revision 31)
+++ protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocolTest.java	(revision 31)
@@ -0,0 +1,184 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.math.BigInteger;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import geniusweb.events.ProtocolEvent;
+import geniusweb.inform.Agreements;
+import geniusweb.protocol.CurrentNegoState;
+import geniusweb.protocol.NegoState;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.SessionState;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProtocolRef;
+import tudelft.utilities.listener.Listener;
+import tudelft.utilities.logging.ReportToLogger;
+
+public class AllPermutationsLearnProtocolTest {
+
+	private final AllPermutationsLearnState state = mock(
+			AllPermutationsLearnState.class),
+			newstate = mock(AllPermutationsLearnState.class);
+	private final ReportToLogger log = new ReportToLogger("test");
+	private final AllPermutationsLearnProtocol app = new AllPermutationsLearnProtocol(
+			state, log);
+	private final ProtocolToPartyConnFactory factory = mock(
+			ProtocolToPartyConnFactory.class);
+	private final SessionSettings settings = mock(SessionSettings.class);
+	private final SessionState finalsessionstate = mock(SessionState.class);
+	private Agreements agreement = mock(Agreements.class);
+	private final long NOW = 1000;
+	private SessionResult finalstate = mock(SessionResult.class);
+
+	@Before
+	public void before() {
+		when(state.getSize()).thenReturn(BigInteger.ZERO);
+		when(finalsessionstate.isFinal(anyLong())).thenReturn(true);
+		when(finalsessionstate.getSettings()).thenReturn(settings);
+		when(finalsessionstate.getAgreements()).thenReturn(agreement);
+		when(finalsessionstate.getResult()).thenReturn(finalstate);
+		when(finalstate.getAgreements()).thenReturn(agreement);
+		when(state.with(any())).thenReturn(newstate);
+	}
+
+	@Test
+	public void smokeTest() {
+	}
+
+	@Test
+	public void startTestNoSessions() {
+		AllPermutationsLearnProtocol app = new AllPermutationsLearnProtocol(
+				state, log);
+		app.start(factory);
+
+	}
+
+	@Test
+	public void getDescrTest() {
+		assertNotNull(app.getDescription());
+	}
+
+	@Test
+	public void getStateTest() {
+		assertEquals(state, app.getState());
+	}
+
+	@Test
+	public void getRefTest() {
+		assertEquals("APPLearn", app.getRef().getURI().toString());
+	}
+
+	@Test
+	public void runOneSessionTest() throws InterruptedException {
+		MockSessionProtocol sessionprotocol = startSession();
+		sessionprotocol.getListener();
+	}
+
+	@Test
+	public void runOneSessionAndUnhandledEvent() throws InterruptedException {
+		MockSessionProtocol sessionprotocol = startSession();
+		sessionprotocol.getListener().notifyChange(mock(ProtocolEvent.class));
+		verify(state, times(0)).with(any());
+	}
+
+	/**
+	 * Check that a started session correctly handles a session event.
+	 */
+	@Test
+	public void CurrentNegoStateEventTest() throws InterruptedException {
+		MockSessionProtocol sessionprotocol = startSession();
+
+		CurrentNegoState evt = mock(CurrentNegoState.class);
+		when(evt.getState()).thenReturn(finalsessionstate);
+		sessionprotocol.getListener().notifyChange(evt);
+
+		ArgumentCaptor<SessionResult> argument = ArgumentCaptor
+				.forClass(SessionResult.class);
+		verify(state, times(1)).with(argument.capture());
+		assertEquals(agreement, argument.getValue().getAgreements());
+		assertNull(argument.getValue().getError());
+	}
+
+	private MockSessionProtocol startSession() throws InterruptedException {
+		SessionSettings setting = mock(SessionSettings.class);
+		MockSessionProtocol sessionprotocol = new MockSessionProtocol();
+		when(setting.getProtocol(log)).thenReturn(sessionprotocol);
+		when(state.getNextSettings()).thenReturn(setting);
+
+		app.start(factory);
+		Thread.sleep(500); // allow app to start session
+		// this call should return fine, and have listener registered to our
+		// protocol. app now waits for a notification
+		return sessionprotocol;
+	}
+
+}
+
+/**
+ * 
+ * The protocol needs to record the listners, we need a listener to trigger
+ * further protocol actions. Mockito can't handle this, we mneed our own mock...
+ *
+ */
+class MockSessionProtocol implements SessionProtocol {
+	private final List<Listener<ProtocolEvent>> listeners = new LinkedList<>();
+
+	@Override
+	public void start(ProtocolToPartyConnFactory connectionfactory) {
+
+	}
+
+	@Override
+	public String getDescription() {
+		return null;
+	}
+
+	@Override
+	public NegoState getState() {
+		return null;
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return null;
+	}
+
+	@Override
+	public void addParticipant(PartyWithProfile party) {
+	}
+
+	@Override
+	public void addListener(Listener<ProtocolEvent> l) {
+		listeners.add(l);
+	}
+
+	@Override
+	public void removeListener(Listener<ProtocolEvent> l) {
+	}
+
+	public Listener<ProtocolEvent> getListener() {
+		if (listeners.size() != 1) {
+			throw new IllegalStateException("Not having exactly 1 listener");
+		}
+		return listeners.get(0);
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettingsTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettingsTest.java	(revision 31)
+++ protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettingsTest.java	(revision 31)
@@ -0,0 +1,251 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.deadline.DeadlineTime;
+import geniusweb.protocol.NegoSettings;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.learn.LearnSettings;
+import geniusweb.protocol.session.saop.SAOPSettings;
+import geniusweb.protocol.tournament.ProfileList;
+import geniusweb.protocol.tournament.Team;
+import geniusweb.protocol.tournament.TournamentSettings;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyRef;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.ProfileRef;
+import tudelft.utilities.immutablelist.ImmutableList;
+import tudelft.utilities.junit.GeneralTests;
+import tudelft.utilities.logging.ReportToLogger;
+
+public class AllPermutationsLearnSettingsTest
+		extends GeneralTests<AllPermutationsLearnSettings> {
+
+	private final ObjectMapper jackson = new ObjectMapper();
+
+	private final String serialized = "{\"AllPermutationsLearnSettings\":{\"teams\":[{\"Team\":[{\"partyref\":\"party1\",\"parameters\":{\"persistentstate\":\"a48b8bc9-4275-4a4e-bee9-6c0a734dc99a\",\"negotiationdata\":[]}}]},{\"Team\":[{\"partyref\":\"party2\",\"parameters\":{\"persistentstate\":\"12b6af6c-b8a0-4731-8b37-0454cafeb7f4\",\"negotiationdata\":[]}}]}],\"profileslists\":[{\"ProfileList\":[\"profile1\"]},{\"ProfileList\":[\"profile2\"]},{\"ProfileList\":[\"profile3\"]}],\"reuseTeams\":false,\"teamsPerSession\":2,\"sessionsettings\":{\"SAOPSettings\":{\"participants\":[],\"deadline\":{\"deadlinetime\":{\"durationms\":10000}}}},\"numberTournaments\":1,\"learnSettings\":{\"LearnSettings\":{\"participants\":[],\"deadline\":{\"deadlinetime\":{\"durationms\":99000}}}}}}";
+	private AllPermutationsLearnSettings settings, settings1a, settings2,
+			settings3, settings4, settings5;
+
+	// use directly because there is some hard coding currently.
+	private SAOPSettings saopsettings;
+
+	private ProfileRef profile1, profile2, profile3;
+	private ProfileList profiles1, profiles2, profiles3;
+	private List<Team> parties, parties3;
+
+	private LearnSettings learnsettings;
+
+	private List<ProfileList> profiles;
+
+	@Before
+	public void before() throws URISyntaxException {
+
+		PartyRef partyref1 = new PartyRef("party1");
+		PartyRef partyref2 = new PartyRef("party2");
+		PartyRef partyref3 = new PartyRef("party3");
+
+		Parameters params1 = new Parameters()
+				.with("persistentstate", "a48b8bc9-4275-4a4e-bee9-6c0a734dc99a")
+				.with("negotiationdata", Collections.emptyList());
+		Parameters params2 = new Parameters()
+				.with("persistentstate", "12b6af6c-b8a0-4731-8b37-0454cafeb7f4")
+				.with("negotiationdata", Collections.emptyList());
+		Parameters params3 = new Parameters()
+				.with("persistentstate", "2b9488ed-c06b-4027-826b-4528254558f1")
+				.with("negotiationdata", Collections.emptyList());
+
+		PartyWithParameters pwithp1 = new PartyWithParameters(partyref1,
+				params1);
+		PartyWithParameters pwithp2 = new PartyWithParameters(partyref2,
+				params2);
+		PartyWithParameters pwithp3 = new PartyWithParameters(partyref3,
+				params3);
+		Team partywithparams1 = new Team(Arrays.asList(pwithp1));
+		Team partywithparams2 = new Team(Arrays.asList(pwithp2));
+		Team partywithparams3 = new Team(Arrays.asList(pwithp3));
+
+		parties = Arrays.asList(partywithparams1, partywithparams2);
+		parties3 = Arrays.asList(partywithparams1, partywithparams2,
+				partywithparams3);
+		DeadlineTime deadline = new DeadlineTime(10000);
+
+		saopsettings = new SAOPSettings(Collections.emptyList(), deadline);
+
+		profile1 = new ProfileRef("profile1");
+		profile2 = new ProfileRef("profile2");
+		profile3 = new ProfileRef("profile3");
+		profiles1 = new ProfileList(Arrays.asList(profile1));
+		profiles2 = new ProfileList(Arrays.asList(profile2));
+		profiles3 = new ProfileList(Arrays.asList(profile3));
+
+		learnsettings = new LearnSettings(Collections.emptyList(),
+				new DeadlineTime(99000));
+
+		profiles = Arrays.asList(profiles1, profiles2, profiles3);
+
+		settings = new AllPermutationsLearnSettings(parties, profiles, false, 2,
+				saopsettings, 1, learnsettings);
+
+		settings1a = new AllPermutationsLearnSettings(parties, profiles, false,
+				2, saopsettings, 1, learnsettings);
+
+		settings2 = new AllPermutationsLearnSettings(
+				Arrays.asList(partywithparams1, partywithparams2,
+						partywithparams3),
+				profiles, false, 2, saopsettings, 1, learnsettings);
+
+		settings3 = new AllPermutationsLearnSettings(parties, profiles, true, 2,
+				saopsettings, 1, learnsettings);
+
+		settings4 = new AllPermutationsLearnSettings(parties,
+				Arrays.asList(profiles1, profiles2, profiles3, profiles1),
+				false, 2, saopsettings, 1, learnsettings);
+
+		settings5 = new AllPermutationsLearnSettings(parties, profiles, false,
+				2, saopsettings, 2, learnsettings);
+
+	}
+
+	@Override
+	public List<List<AllPermutationsLearnSettings>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(settings, settings1a),
+				Arrays.asList(settings2), Arrays.asList(settings3),
+				Arrays.asList(settings4), Arrays.asList(settings5));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"AllPermutationsLearnSettings.*party1.*party2.*,false,.*profile1.*profile2.*profile3.*,2,SAOPSettings.*1.*",
+				"AllPermutationsLearnSettings.*party1.*party2.*party3.*,false,.*profile1.*profile2.*profile3.*,2,SAOPSettings.*1.*",
+				"AllPermutationsLearnSettings.*party1.*party2.*,true,.*profile1.*profile2.*profile3.*,2,SAOPSettings.*1.*",
+				"AllPermutationsLearnSettings.*party1.*party2.*,false,.*profile1.*profile2.*profile3.*profile1.*,2,SAOPSettings.*1.*",
+				"AllPermutationsLearnSettings.*party1.*party2.*,false,.*profile1.*profile2.*profile3.*,2,SAOPSettings.*2.*");
+	}
+
+	@Test
+	public void getProtocolTest() {
+		assertEquals("APPLearn",
+				settings.getProtocol(new ReportToLogger("test")).getRef()
+						.getURI().toString());
+	}
+
+	@Test
+	public void testDeserialize() throws IOException {
+		TournamentSettings obj = (TournamentSettings) jackson
+				.readValue(serialized, NegoSettings.class);
+		System.out.println(obj);
+		assertEquals(settings, obj);
+	}
+
+	@Test
+	public void testSerialize()
+			throws JsonProcessingException, URISyntaxException {
+
+		String string = jackson.writeValueAsString(settings);
+		System.out.println(string);
+		assertEquals(serialized, string);
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testNullParties() {
+		new AllPermutationsLearnSettings(null,
+				Arrays.asList(profiles1, profiles2, profiles3, profiles1), true,
+				2, saopsettings, 1, learnsettings);
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testNoParties() {
+		new AllPermutationsLearnSettings(Collections.emptyList(),
+				Arrays.asList(profiles1, profiles2, profiles3, profiles1), true,
+				2, saopsettings, 1, learnsettings);
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testNullProfiles() {
+		new AllPermutationsLearnSettings(parties, null, true, 2, saopsettings,
+				1, learnsettings);
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testInsufficientProfiles() {
+		new AllPermutationsLearnSettings(parties,
+				Arrays.asList(profiles1, profiles2, profiles3, profiles1), true,
+				4, saopsettings, 1, learnsettings);
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testNullSessionSettings() {
+		new AllPermutationsLearnSettings(parties,
+				Arrays.asList(profiles1, profiles2, profiles3, profiles1), true,
+				4, null, 1, learnsettings);
+
+	}
+
+	@SuppressWarnings("unused")
+	@Test(expected = IllegalArgumentException.class)
+	public void testOnePartyPerSession() {
+		new AllPermutationsLearnSettings(parties,
+				Arrays.asList(profiles1, profiles2, profiles3, profiles1), true,
+				1, saopsettings, 1, learnsettings);
+
+	}
+
+	@Test
+	public void getPermutationsTest() {
+		// #permutations + 1 training session
+		assertEquals(7, settings.permutations().size().intValue());
+	}
+
+	/**
+	 * Test the learn session
+	 */
+	@Test
+	public void testGetlearnSessions() {
+		// particularly we want to test where numparties does not match
+		// numteamspersession
+		AllPermutationsLearnSettings settings3parties = new AllPermutationsLearnSettings(
+				parties3, profiles, false, 2, saopsettings, 2, learnsettings);
+		ImmutableList<SessionSettings> perms = settings3parties.permutations();
+		// 3 parties, 3 profiles, 2 parties per session, 2 tournaments = 2*18
+		assertEquals(37, perms.size().intValue());
+		LearnSettings learnsetting = (LearnSettings) perms
+				.get(BigInteger.valueOf(36));
+		assertEquals(3, learnsetting.getAllParties().size());
+		Parameters party1params = learnsetting.getAllParties().get(0).getParty()
+				.getParameters();
+		assertEquals("a48b8bc9-4275-4a4e-bee9-6c0a734dc99a",
+				party1params.get("persistentstate").toString());
+		List negodata = (List) party1params.get("negotiationdata");
+		assertEquals(24, negodata.size());
+	}
+
+	@Test
+	public void getMaxRuntimeTest() {
+		assertEquals((Double) (6d * (10 + 2) + 99d), settings.getMaxRunTime());
+	}
+
+}
Index: protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnStateTest.java
===================================================================
--- protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnStateTest.java	(revision 31)
+++ protocol/src/test/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnStateTest.java	(revision 31)
@@ -0,0 +1,102 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import geniusweb.actions.PartyId;
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.references.PartyWithProfile;
+import tudelft.utilities.immutablelist.ImmutableList;
+
+public class AllPermutationsLearnStateTest {
+
+	private final AllPermutationsLearnSettings toursettings = mock(
+			AllPermutationsLearnSettings.class);;
+	@SuppressWarnings("unchecked")
+	private final List<SessionResult> results = mock(List.class);
+	@SuppressWarnings("unchecked")
+	private final ImmutableList<SessionSettings> settings = mock(
+			ImmutableList.class);
+	private final AllPermutationsLearnState state = new AllPermutationsLearnState(
+			toursettings, settings, results);
+	private final SessionSettings session7settings = mock(
+			SessionSettings.class);
+	private final long NOW = 1000;
+	private PartyWithProfile pp1 = mock(PartyWithProfile.class);
+	private PartyWithProfile pp2 = mock(PartyWithProfile.class);
+
+	@SuppressWarnings("unchecked")
+	private final List<PartyWithProfile> participants = Arrays.asList(pp1, pp2);
+
+	@Test
+	public void getResultsTest() {
+		assertEquals(results, state.getResults());
+	}
+
+	@Test
+	public void isFinalTestFalse() {
+		when(settings.size()).thenReturn(BigInteger.TEN);
+		when(results.size()).thenReturn(7);
+		assertFalse(state.isFinal(NOW));
+	}
+
+	@Test
+	public void isFinalTestTrue() {
+		when(settings.size()).thenReturn(BigInteger.TEN);
+		when(results.size()).thenReturn(10);
+		assertTrue(state.isFinal(NOW));
+	}
+
+	@Test
+	public void getNextSetting() {
+		prepareNextSettings();
+		assertEquals(session7settings, state.getNextSettings());
+	}
+
+	private void prepareNextSettings() {
+		when(results.size()).thenReturn(7); // result 0..6
+		when(settings.size()).thenReturn(BigInteger.TEN);
+		// when(session7settings.getTeams()).thenReturn(participants);
+		when(session7settings.getAllParties()).thenReturn(participants);
+		when(settings.get(BigInteger.valueOf(7))).thenReturn(session7settings);
+
+	}
+
+	@Test
+	public void withTest() {
+		prepareNextSettings();
+		ArrayList<SessionResult> results1 = new ArrayList<>();
+		for (int n = 0; n < 7; n++) {
+			results1.add(mock(SessionResult.class));
+		}
+		// use state with "real" results... to allow array copy in with
+		AllPermutationsLearnState state0 = new AllPermutationsLearnState(
+				toursettings, settings, results1);
+
+		when(settings.size()).thenReturn(BigInteger.TEN);
+		when(settings.get(BigInteger.valueOf(7))).thenReturn(session7settings);
+
+		SessionResult result = mock(SessionResult.class);
+
+		Map<PartyId, PartyWithProfile> participants3 = mock(Map.class);
+		when(participants3.values()).thenReturn(participants);
+		when(result.getParticipants()).thenReturn(participants3);
+		AllPermutationsLearnState state1 = state0.with(result);
+
+		assertEquals(8, state1.getResults().size());
+		assertEquals(toursettings, state1.getSettings());
+
+	}
+}
