package geniusweb.profilesserver.websocket;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import geniusweb.bidspace.AllBidsList;
import geniusweb.bidspace.BidsWithUtility;
import geniusweb.issuevalue.Bid;
import geniusweb.profile.DefaultPartialOrdering;
import geniusweb.profile.PartialOrdering;
import geniusweb.profile.Profile;
import geniusweb.profile.utilityspace.LinearAdditive;
import geniusweb.profilesserver.Jackson;
import geniusweb.profilesserver.ProfilesRepository;
import geniusweb.profilesserver.events.ChangeEvent;
import tudelft.utilities.immutablelist.FixedList;
import tudelft.utilities.immutablelist.ImmutableList;
import tudelft.utilities.listener.Listener;
/**
* Returns a websocket that communicates the list of currently available domains
* and profiles. Every time something changes, a new list of domains and
* profiles is sent. For each new websocket the server will create one of this
* but they all share one {@link ProfilesRepository}.
*
*
* Query string: the websocket allows query strings.
*
* - partial
a query key that is assigned a natural number N (0 or
* larger). If set, the profile is converted into a Partial Profile with the
* given number of points. If N>0, the maximum utility bid is included .If N>1,
* the minimum utility bid is included. The other bids are picked at random.
* This filtering mechanism only works if the profile is {@link LinearAdditive}
* space. The reservation bid is passed through unmodified.
*/
@ServerEndpoint("/websocket/get/{domain}/{profile}")
public class GetProfileSocket {
private Profile prof = null; // the latest that we sent to client.
// should all be final, except that we can only set them when start is
// called...
private String profilename;
private Listener changeListener;
private Session session;
private Map params = Collections.emptyMap();
@OnOpen
public void start(Session session, @PathParam("domain") final String domain,
@PathParam("profile") final String profile) throws IOException {
this.session = session;
this.profilename = domain + "/" + profile;
if (session.getQueryString() != null) {
List paramstrings = Arrays
.asList(session.getQueryString().split("&"));
params = paramstrings.stream().map(str -> str.split("=")).collect(
Collectors.toMap(vals -> vals[0], vals -> vals[1]));
}
changeListener = new Listener() {
@Override
public void notifyChange(ChangeEvent data) {
sendupdatedProfile();
}
};
sendupdatedProfile();
Profiles.repository.addListener(changeListener);
}
private void sendupdatedProfile() {
Profile newprof = Profiles.repository.getProfile(profilename);
// notice, may be null if profile does not exist/was removed.
if (newprof == null ? prof != null : !newprof.equals(prof)) {
prof = newprof;
try {
session.getBasicRemote().sendText(
Jackson.instance().writeValueAsString(filter(prof)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*
* @param prof1
* @return a filtered profile. If "partial" is not set, this returns the
* given profile unmodified. If partial is set, a
* DefaultPartialOrdering with the requested number of bids are
* selected
*/
private Profile filter(Profile prof1) {
String partial = params.get("partial");
if (partial == null) {
return prof1;
}
if (!(prof1 instanceof PartialOrdering))
throw new IllegalArgumentException(
"profile must be partialordering but got " + prof);
PartialOrdering profile = (PartialOrdering) prof1;
final int numbids = Integer.parseInt(partial);
if (numbids < 0)
throw new IllegalArgumentException("parameter partial must be >=0");
ImmutableList allbids;
if (profile instanceof DefaultPartialOrdering)
allbids = new FixedList(
((DefaultPartialOrdering) profile).getBids());
else
allbids = new AllBidsList(prof.getDomain());
if (BigInteger.valueOf(numbids).compareTo(allbids.size()) > 0)
throw new IllegalArgumentException("Request for " + numbids
+ " exceeds number of bids in the space " + allbids.size());
BidsWithUtility info = new BidsWithUtility((LinearAdditive) prof);
Set selected = new HashSet<>();
if (numbids > 0) {
selected.add(info.getExtremeBid(true));
}
if (numbids > 1) {
selected.add(info.getExtremeBid(false));
}
Random random = new Random();
for (int nr = 2; nr < numbids; nr++) {
int attempt = 0;
Bid bid;
do {
long i = random.nextInt(allbids.size().intValue());
bid = allbids.get(BigInteger.valueOf(i));
} while (attempt++ < 10 && selected.contains(bid));
selected.add(bid);
}
// put comparison info for these bids in the map
Map> isBetterMap = new HashMap<>();
for (Bid bid : selected) {
Set worse = selected.stream().filter(
otherbid -> profile.isPreferredOrEqual(bid, otherbid)
&& !profile.isPreferredOrEqual(otherbid, bid))
.collect(Collectors.toSet());
isBetterMap.put(bid, worse);
}
return new DefaultPartialOrdering(
prof.getName() + "-partial-" + numbids, prof.getDomain(),
prof.getReservationBid(), isBetterMap);
}
@OnClose
public void end() throws IOException {
Profiles.repository.removeListener(changeListener);
}
@OnError
public void onError(Throwable t) throws Throwable {
t.printStackTrace();
}
}