package no.java.incogito.web;
import static fj.Function.curry;
import static fj.data.List.list;
import static fj.data.Set.empty;
import static fj.data.Stream.stream;
import static fj.pre.Ord.stringOrd;
import static no.java.incogito.Functions.compose;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import no.java.incogito.Functions;
import no.java.incogito.application.IncogitoConfiguration;
import no.java.incogito.domain.CssConfiguration;
import no.java.incogito.domain.Event;
import no.java.incogito.domain.Label;
import no.java.incogito.domain.Level;
import no.java.incogito.domain.Room;
import no.java.incogito.domain.Schedule;
import no.java.incogito.domain.Session;
import no.java.incogito.domain.SessionId;
import no.java.incogito.domain.UserSessionAssociation;
import no.java.incogito.domain.IncogitoUri.IncogitoEventsUri.IncogitoEventUri;
import no.java.incogito.domain.IncogitoUri.IncogitoRestEventsUri.IncogitoRestEventUri;
import no.java.incogito.domain.Session.Format;
import no.java.incogito.dto.SessionXml;
import no.java.incogito.web.resources.XmlFunctions;
import no.java.incogito.web.servlet.WebCalendar;
import no.java.incogito.web.servlet.WebSessionList;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import fj.F;
import fj.F2;
import fj.F3;
import fj.P;
import fj.P2;
import fj.data.List;
import fj.data.Set;
import fj.data.Stream;
import fj.data.TreeMap;
import fj.pre.Ord;
import fj.pre.Ordering;
/**
* @author <a href="mailto:trygvis@java.no">Trygve Laugstøl</a>
* @version $Id$
*/
public class WebFunctions {
private static final NumberFormat oneDigitFormat;
static {
oneDigitFormat = DecimalFormat.getNumberInstance(Locale.ENGLISH);
oneDigitFormat.setMaximumFractionDigits(1);
oneDigitFormat.setMinimumFractionDigits(1);
}
// -----------------------------------------------------------------------
// Calendar CSS
// -----------------------------------------------------------------------
public static final F<CssConfiguration, F<List<Room>, List<String>>> generateCalendarCss = curry(new F2<CssConfiguration, List<Room>, List<String>>() {
public List<String> f(CssConfiguration cssConfiguration, List<Room> roomList) {
List<String> sessions = Functions.List_product(list("09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"),
list("00", "15", "30", "45"), P.<String, String>p2()).
zipIndex().
map(hourToSessionCss.f(cssConfiguration));
Stream<String> durations = stream(10, 60).zapp(Stream.repeat(durationToCss.f(cssConfiguration)));
return List.join(list(sessions, durations.toList()));
}
});
public static final F<CssConfiguration, F<Integer, String>> durationToCss = curry(new F2<CssConfiguration, Integer, String>() {
public String f(CssConfiguration cssConfiguration, Integer minutes) {
return ".duration" + minutes + " { height: 10em; margin: 0; padding: 0; }";
}
});
public static final F<CssConfiguration, F<P2<P2<String, String>, Integer>, String>> hourToSessionCss = curry(new F2<CssConfiguration, P2<P2<String, String>, Integer>, String>() {
F<P2<String, String>, String> prepend = P2.tuple(Functions.prepend);
public String f(CssConfiguration cssConfiguration, P2<P2<String, String>, Integer> p) {
double em = cssConfiguration.sessionEmStart + (cssConfiguration.getHeightInEm(p._2() * 10));
return ".start" + prepend.f(p._1()) + " { top: " + oneDigitFormat.format(em) + "em; }";
}
});
// -----------------------------------------------------------------------
// Session CSS
// -----------------------------------------------------------------------
public static F<IncogitoConfiguration, F<Event, List<String>>> generateSessionCss = curry( new F2<IncogitoConfiguration, Event, List<String>>() {
public List<String> f(final IncogitoConfiguration configuration, final Event event) {
List<String> formats = list(Session.Format.values()).
map(Session.Format.toString).
map(new F<String, String>() {
public String f(String format) {
return ".format-" + format + " { list-style-image: url('" + configuration.baseurl + "/images/icons/session-format-" + format.toLowerCase() + "-small.png'); }";
}
});
List<String> levels = event.levels.values().
map(Level.showId.showS_()).
map(new F<String, String>() {
public String f(String level) {
return ".level-" + level + " { list-style-image: url('" + configuration.baseurl + "/rest/events/" + event.name + "/icons/levels/" + level + ".png'); }";
}
});
List<String> labels = event.labels.
map(new F<Label, String>() {
public String f(Label label) {
return ".label-" + label.id + " { list-style-image: url('" + configuration.baseurl + "/rest/events/" + event.name + "/icons/labels/" + label.id + ".png'); }";
}
});
return formats.append(levels).append(labels);
}
});
// -----------------------------------------------------------------------
// Calendar
// -----------------------------------------------------------------------
public static final F<IncogitoRestEventUri, F<IncogitoEventUri, F<Schedule, WebCalendar>>> webCalendar = curry(new F3<IncogitoRestEventUri, IncogitoEventUri, Schedule, WebCalendar>() {
public WebCalendar f(IncogitoRestEventUri restEventUri, IncogitoEventUri eventUri, Schedule schedule) {
F<Session,SessionXml> sessionToXml = XmlFunctions.sessionToXml.f(restEventUri).f(eventUri);
Map<String, String> attendanceMap = new HashMap<String, String>();
for (P2<SessionId, UserSessionAssociation> sessionAssociation : schedule.sessionAssociations) {
attendanceMap.put(sessionAssociation._1().value, sessionAssociation._2().interestLevel.name());
}
LinkedHashMap<LocalDate, Collection<String>> roomsByDate = new LinkedHashMap<LocalDate, Collection<String>>();
LinkedHashMap<LocalDate, Collection<Interval>> timeslotsByDate = new LinkedHashMap<LocalDate, Collection<Interval>>();
TreeMap<LocalDate, TreeMap<String, List<SessionXml>>> dayToRoomToPresentationsMap = TreeMap.empty(Functions.LocalDate_ord);
TreeMap<LocalDate, List<SessionXml>> quickiesByDay = TreeMap.empty(Functions.LocalDate_ord);
for (final P2<LocalDate, Integer> dayIndex : schedule.event.dates.zipIndex()) {
final LocalDate day = dayIndex._1();
final List<Room> rooms = schedule.event.roomsByDate.index(dayIndex._2());
final List<Interval> timeslots = schedule.event.timeslotsByDate.index(dayIndex._2());
roomsByDate.put(day, rooms.map(Room.name_).toCollection());
timeslotsByDate.put(day, timeslots.toCollection());
// -----------------------------------------------------------------------
// Presentations
// -----------------------------------------------------------------------
F<Session, Boolean> presentationFilter = new F<Session, Boolean>() {
public Boolean f(Session session) {
return session.timeslot.isSome() &&
session.timeslot.some().getStart().toLocalDate().equals(day) &&
session.room.isSome() &&
rooms.find(compose(Functions.equals.f(session.room.some()), Room.name_)).isSome() &&
(session.format.equals(Format.Presentation) || session.format.equals(Format.BoF));
}
};
TreeMap<String, List<SessionXml>> roomToSessionMap = TreeMap.empty(stringOrd);
// Create an empty list for each day, just to make sure that every day is covered. Other parts rely on this fact
List<SessionXml> emptyList = List.nil();
for (Room room : rooms) {
roomToSessionMap = roomToSessionMap.set(room.name, emptyList);
}
// For each session, find the room's list and add the session to the list
for (SessionXml session : schedule.sessions.filter(presentationFilter).map(sessionToXml)) {
roomToSessionMap = roomToSessionMap.set(session.room, roomToSessionMap.get(session.room).some().cons(session));
}
dayToRoomToPresentationsMap = dayToRoomToPresentationsMap.set(dayIndex._1(), roomToSessionMap);
// -----------------------------------------------------------------------
// Lightning Talks
// -----------------------------------------------------------------------
// Do not check against the room list, just assume it is ok.
F<Session, Boolean> lightningTalkFilter = new F<Session, Boolean>() {
public Boolean f(Session session) {
return session.timeslot.isSome() &&
session.timeslot.some().getStart().toLocalDate().equals(day) &&
session.room.isSome() &&
session.format.equals(Format.Quickie);
}
};
Set<Session> quickies = empty(reverseSessionTimestampOrd);
for (Session session : schedule.sessions.filter(lightningTalkFilter)) {
quickies = quickies.insert(session);
}
quickiesByDay = quickiesByDay.set(day, quickies.toList().map(sessionToXml));
}
return new WebCalendar(attendanceMap, roomsByDate, timeslotsByDate,
dayToRoomToPresentationsMap, quickiesByDay);
}
});
// WebSessionList
public static final F<IncogitoRestEventUri, F<IncogitoEventUri, F<Schedule, WebSessionList>>> webSessionList = curry(new F3<IncogitoRestEventUri, IncogitoEventUri, Schedule, WebSessionList>() {
public WebSessionList f(IncogitoRestEventUri restEventUri, IncogitoEventUri eventUri, Schedule schedule) {
F<Session,SessionXml> sessionToXml = XmlFunctions.sessionToXml.f(restEventUri).f(eventUri);
LinkedHashMap<LocalDate, Collection<Interval>> timeslotsByDate = new LinkedHashMap<LocalDate, Collection<Interval>>();
LinkedHashMap<LocalDate, LinkedHashMap <Interval, List<SessionXml>>> sessionsByTimeSlotByDate = new LinkedHashMap<LocalDate, LinkedHashMap<Interval,List<SessionXml>>>();
for (final P2<LocalDate, Integer> dayIndex : schedule.event.dates.zipIndex()) {
final LocalDate day = dayIndex._1();
final List<Room> rooms = schedule.event.roomsByDate.index(dayIndex._2());
final List<Interval> timeslots = schedule.event.timeslotsByDate.index(dayIndex._2());
timeslotsByDate.put(day, timeslots.toCollection());
F<Session, Boolean> presentationFilter = new F<Session, Boolean>() {
public Boolean f(Session session) {
return session.timeslot.isSome() &&
session.timeslot.some().getStart().toLocalDate().equals(day) &&
session.room.isSome() &&
rooms.find(compose(Functions.equals.f(session.room.some()), Room.name_)).isSome() &&
(session.format.equals(Format.Presentation) || session.format.equals(Format.BoF));
}
};
F<Session, Boolean> lightningTalkFilter = new F<Session, Boolean>() {
public Boolean f(Session session) {
return session.timeslot.isSome() &&
session.timeslot.some().getStart().toLocalDate().equals(day) &&
session.room.isSome() &&
session.format.equals(Format.Quickie);
}
};
// Create an empty list for each day, just to make sure that every day is covered. Other parts rely on this fact
LinkedHashMap<Interval, List<SessionXml>> sessionByTimeSlotsMap =
new LinkedHashMap<Interval, List<SessionXml>>();
List<Session> quickies = schedule.sessions.filter(lightningTalkFilter);
for(final Interval timeslot:timeslots) {
F<Session, Boolean> quickieToTimeslotFilter = new F<Session, Boolean>() {
public Boolean f(Session session) {
return session.timeslot.some().overlaps(timeslot);
}
};
// putter lyntaler inn per sesjon
sessionByTimeSlotsMap.put(timeslot, quickies.filter(quickieToTimeslotFilter).sort(sessionTimestampOrd).map(sessionToXml));
}
for (Session session : schedule.sessions.filter(presentationFilter).sort(sessionRoomOrd)) {
List<SessionXml> list = sessionByTimeSlotsMap.get(session.timeslot.some());
if (list != null){
sessionByTimeSlotsMap.put(session.timeslot.some(), list.cons(sessionToXml.f(session)));
}
}
sessionsByTimeSlotByDate.put(day, sessionByTimeSlotsMap);
}
return new WebSessionList(timeslotsByDate, sessionsByTimeSlotByDate);
}
});
public static final Ord<Session> reverseSessionTimestampOrd = Ord.ord(curry(new F2<Session, Session, Ordering>() {
public Ordering f(Session a, Session b) {
return Ord.longOrd.compare(b.timeslot.some().getStartMillis(), a.timeslot.some().getStartMillis());
}
}));
public static final Ord<Session> sessionTimestampOrd = Ord.ord(curry(new F2<Session, Session, Ordering>() {
public Ordering f(Session a, Session b) {
return Ord.longOrd.compare(a.timeslot.some().getStartMillis(), b.timeslot.some().getStartMillis());
}
}));
public static final Ord<Session> sessionRoomOrd = Ord.ord(curry(new F2<Session, Session, Ordering>() {
public Ordering f(Session a, Session b) {
return Ord.stringOrd.compare(b.room.some(), a.room.some());
}
}));
}