package geniusweb.protocol.session.mopac;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import geniusweb.actions.Action;
import geniusweb.actions.PartyId;
import geniusweb.inform.Agreements;
import geniusweb.inform.OptIn;
import geniusweb.progress.Progress;
import geniusweb.progress.ProgressRounds;
import geniusweb.protocol.NegoProtocol;
import geniusweb.protocol.ProtocolException;
import geniusweb.protocol.session.SessionResult;
import geniusweb.protocol.session.SessionState;
import geniusweb.protocol.session.mopac.phase.OfferPhase;
import geniusweb.protocol.session.mopac.phase.OptInPhase;
import geniusweb.protocol.session.mopac.phase.Phase;
import geniusweb.protocol.session.saop.SAOPSettings;
import geniusweb.references.PartyWithProfile;
/**
* Keeps track of the current {@link Phase}. Adds initializing stuff and
* time/deadline checking. This state does not contain connections, this assumes
* that someone else handles (i.e. {@link NegoProtocol} that and the connections
* with the parties and that negotiation events are just pumped in from there.
*
* This object is a bit tricky. It has two states
*
* - The initial state, where phase=null and connections are being made to all
* parties. At this point, problems can not be handled nicely yet because there
* are no PartyId's for problematic parties
*
- The working state, where all parties are connected and all problems can
* be connected to an offending party.
*
*/
public class MOPACState implements SessionState {
private final Phase phase; // maybe null while initializing
private final MOPACSettings settings;
private final Map partyprofiles;
private final List actions;
private final Progress progress;
/**
* Creates the initial state from the given settings and progress=null
*
* @param settings the {@link SAOPSettings}
*/
public MOPACState(MOPACSettings settings) {
this(null, Arrays.asList(), null, settings, Collections.emptyMap());
}
/**
* @param phase The Phase, or null if still initializing. Phase is
* set something only after we have connections.
* @param actions the legal actions that have been done in the
* negotiation. {@link PartyStates#getActions()} is
* called to collect the actions after the phase is
* finished.first action in list is the oldest. This
* will not contain actions that immediately led to an
* agreement, because {@link PartyStates} removes the
* actions that led to an agreement. This MUST NOT
* contain illegal actions. Parties doing illegal
* actions are killed and the offending action ends up
* in the stacktrace. Previous actions of crashed
* parties remain standing and valid.
* @param progress the {@link Progress} line. can be null if not yet
* known. null happens because during initialization
* phase the protocol first add all connections. During
* this time, parties may already enter the
* failed/exception state.
* @param settings the {@link SAOPSettings}
* @param partyprofiles map with the {@link PartyWithProfile} for connected
* parties. null is equivalent to an empty map.
*/
@JsonCreator
protected MOPACState(@JsonProperty("phase") Phase phase,
@JsonProperty("actions") List actions,
@JsonProperty("progress") Progress progress,
@JsonProperty("settings") MOPACSettings settings,
@JsonProperty("partyprofiles") Map partyprofiles) {
this.phase = phase;
this.actions = actions;
this.progress = progress;
this.settings = settings;
this.partyprofiles = partyprofiles;
}
/**
* Sets the progress for this session and initial phase. Must be called
* after all parties have been connected with
* {@link #with(PartyId, PartyWithProfile)}.
*
* @param newprogress the initial {@link Progress} typically matching the
* settings deadline object
* @param now current time ms since 1970
*
* @return state with the initial partystates , progress set.
*/
public MOPACState initPhase(Progress newprogress, long now) {
if (progress != null || newprogress == null || phase != null) {
throw new IllegalArgumentException(
"progress must be null, newprogress must be not null and phase must be INIT");
}
PartyStates partyStates = new PartyStates(getPowers());
Phase firstPhase = new OfferPhase(partyStates,
now + getAvailablePhaseTime(newprogress, now),
settings.getVotingEvaluator());
return new MOPACState(firstPhase, actions, newprogress, settings,
partyprofiles);
}
@Override
public List getActions() {
return Collections.unmodifiableList(actions);
}
@Override
public Progress getProgress() {
return progress;
}
@Override
public Agreements getAgreements() {
return phase.getPartyStates().getAgreements();
}
@Override
public MOPACSettings getSettings() {
return settings;
}
public Map getPartyProfiles() {
return Collections.unmodifiableMap(partyprofiles);
}
@Override
public boolean isFinal(long now) {
return phase != null && phase.isFinal(now) && !isNewPhasePossible(now);
}
@Override
public List getResults() {
return Arrays.asList(new SessionResult(partyprofiles, getAgreements(),
Collections.emptyMap(), null));
}
/**
* @param progress the Progress that needs to be checked
* @param now current time ms since 1970
* @return the max possible duration in ms of a phase considering the
* progress.
*/
private static Long getAvailablePhaseTime(Progress aprogress, long now) {
return Math.min(aprogress.getTerminationTime().getTime() - now,
Phase.PHASE_MAXTIME);
}
/**
* @param id the new {@link PartyId}
* @param partyprofile the {@link PartyWithProfile} that is associated with
* this state
* @return new {@link MOPACState} with the new party added. This call
* ignores the progress (does not check isFinal) because we uses
* this during the setup where the deadline is not yet relevant.
*/
protected MOPACState with(PartyId id, PartyWithProfile partyprofile) {
if (phase != null)
throw new IllegalStateException(
"Adding connections only allowed while initializing");
Map newprofiles = new HashMap<>(
partyprofiles);
newprofiles.put(id, partyprofile);
return new MOPACState(null, actions, progress, settings, newprofiles);
}
/**
* @param e the {@link ProtocolException} that occured
* @return a new state with the error set. You MUST have called
* {@link #initPhase(Progress, long)} before using this
*/
public MOPACState with(ProtocolException e) {
return new MOPACState(phase.with(e), actions, progress, settings,
partyprofiles);
}
/**
* Start the next phase. If new phase is OfferPhase, we increase progress.
* Actions is reset to empty. does nothing if not
* {@link #isNewPhasePossible(long)}
*
* @param now current time
* @return new {@link MOPACState} with phase initialized for next phase.
*/
public MOPACState nextPhase(long now) {
long remainingNegoTime = progress.getTerminationTime().getTime() - now;
Phase newphase = phase.next(now,
Math.min(remainingNegoTime, Phase.PHASE_MAXTIME));
return new MOPACState(newphase, actions, increment(progress, phase),
getSettings(), partyprofiles);
}
/**
* When this is called, all parties should have acted.
*
* @param now current time
* @return true if there are still >2 parties active and we have enough
* time for a new phase.
*/
public boolean isNewPhasePossible(long now) {
// System.out.println("phase=" + phase);
Progress newprogress = increment(progress, phase);
if (newprogress.isPastDeadline(now + Phase.PHASE_MINTIME))
return false;
return phase.getPartyStates().getNegotiatingParties().size() >= 2
&& getAvailablePhaseTime(newprogress,
now) > Phase.PHASE_MINTIME;
}
public Phase getPhase() {
return phase;
}
/**
* Check if action is allowed. Add action to the list of actions. Notice,
* this does NOT check if we need to step to the next phase, because
* deciding that is also depending on time-outs.
*
* @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. MUST NOT be null.
* @param action the action that was proposed by actor. MUST NOT be null.
* @param now the current time in ms since 1970, see
* {@link System#currentTimeMillis()}
* @return new {@link MOPACState} with the action checked and registered. If
* the action is not allowed, the new state may be that the actor is
* in the exception list.
*/
public MOPACState with(PartyId actor, Action action, long now) {
return new MOPACState(phase.with(actor, action, now), actions, progress,
settings, partyprofiles);
}
/**
* @param aprogress the progress that might need to be advanced
* @param aphase the phase
* @return the next progress. Progress round advances if phase is
* {@link OptIn}.
*/
private static Progress increment(Progress aprogress, Phase aphase) {
if (aprogress instanceof ProgressRounds && aphase instanceof OptInPhase)
return ((ProgressRounds) aprogress).advance();
return aprogress;
}
/**
* @return the power of all parties as set in their parameters, default =1.
* bad power values (non-integer, or <1) are ignored.
*/
private Map getPowers() {
Map map = new HashMap<>();
for (PartyId pid : partyprofiles.keySet()) {
Object power = partyprofiles.get(pid).getParty().getParameters()
.get("power");
if (power == null || !(power instanceof Integer)
|| ((Integer) power) < 1)
power = 1;
map.put(pid, (Integer) power);
}
return map;
}
@Override
public String toString() {
return "MOPACState[" + phase + "," + settings + "," + partyprofiles
+ "," + progress + "]";
}
/**
*
* @return a wrapped-up state, with all parties doen an action or kicked,
* and agreements collected
*/
public MOPACState finishPhase() {
Phase newphase = phase.finish();
LinkedList newactions = new LinkedList(actions);
newactions.addAll(newphase.getPartyStates().getActions());
return new MOPACState(newphase, newactions, progress, settings,
partyprofiles);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((actions == null) ? 0 : actions.hashCode());
result = prime * result
+ ((partyprofiles == null) ? 0 : partyprofiles.hashCode());
result = prime * result + ((phase == null) ? 0 : phase.hashCode());
result = prime * result
+ ((progress == null) ? 0 : progress.hashCode());
result = prime * result
+ ((settings == null) ? 0 : settings.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;
MOPACState other = (MOPACState) obj;
if (actions == null) {
if (other.actions != null)
return false;
} else if (!actions.equals(other.actions))
return false;
if (partyprofiles == null) {
if (other.partyprofiles != null)
return false;
} else if (!partyprofiles.equals(other.partyprofiles))
return false;
if (phase == null) {
if (other.phase != null)
return false;
} else if (!phase.equals(other.phase))
return false;
if (progress == null) {
if (other.progress != null)
return false;
} else if (!progress.equals(other.progress))
return false;
if (settings == null) {
if (other.settings != null)
return false;
} else if (!settings.equals(other.settings))
return false;
return true;
}
}