package com.thebluealliance.androidclient.helpers; import com.thebluealliance.androidclient.R; import com.thebluealliance.androidclient.TbaLogger; import com.thebluealliance.androidclient.Utilities; import com.thebluealliance.androidclient.comparators.EventSortByDateComparator; import com.thebluealliance.androidclient.comparators.EventSortByTypeAndDateComparator; import com.thebluealliance.androidclient.eventbus.LiveEventUpdateEvent; import com.thebluealliance.androidclient.models.Event; import com.thebluealliance.androidclient.types.EventType; import com.thebluealliance.androidclient.viewmodels.ListSectionHeaderViewModel; import org.greenrobot.eventbus.EventBus; import android.content.Context; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; public final class EventHelper { public static final String CHAMPIONSHIP_LABEL = "Championship Event"; public static final String CITY_CHAMPIONSHIP_LABEL = "%1$s Championship"; public static final String REGIONAL_LABEL = "Week %1$d"; public static final String FLOAT_REGIONAL_LABEL = "Week %1$.1f"; public static final String WEEKLESS_LABEL = "Other Official Events"; public static final String OFFSEASON_LABEL = "%1$s Offseason Events"; public static final String PRESEASON_LABEL = "Preseason Events"; private static final Pattern EVENT_KEY_PATTERN = Pattern.compile("[a-zA-Z]+"); private static final Pattern DISTRICT_EVENT_NAME_PATTERN = Pattern.compile("[A-Z]{2,3} District -(.+)"); private static final Pattern EVENT_EVENT_NAME_PATTERN = Pattern.compile("(.+)Event"); private static final Pattern REGIONAL_EVENT_NAME_PATTERN = Pattern.compile("\\s*(?:MAR |PNW |)(?:FIRST Robotics|FRC|)(.+)(?:(?:District|Regional|Region|State|Tournament|FRC|Field)\\b)"); private static final Pattern FRC_EVENT_NAME_PATTERN = Pattern.compile("(.+)(?:FIRST Robotics|FRC)"); private EventHelper() { // not used } public static boolean validateEventKey(String key) { if (key == null || key.isEmpty()) return false; return key.matches("^[1-9]\\d{3}[a-z,0-9]+$"); } /** * Extracts a short name like "Silicon Valley" from an event name like "Silicon Valley Regional * sponsored by Google.org". * <p> * <p/>See <a href="https://github.com/the-blue-alliance/the-blue-alliance/blob/master/helpers/event_helper.py" * >the server's event_helper.py</a>. */ public static String shortName(String eventName) { Matcher m1 = DISTRICT_EVENT_NAME_PATTERN.matcher(eventName); // XYZ District - NAME if (m1.matches()) { String partial = m1.group(1).trim(); Matcher m2 = EVENT_EVENT_NAME_PATTERN.matcher(partial); // NAME Event... if (m2.lookingAt()) { return m2.group(1).trim(); } return partial; } Matcher m3 = REGIONAL_EVENT_NAME_PATTERN.matcher(eventName); // ... NAME Regional... if (m3.lookingAt()) { String partial = m3.group(1); Matcher m4 = FRC_EVENT_NAME_PATTERN.matcher(partial); // NAME FIRST Robotics/FRC... if (m4.lookingAt()) { return m4.group(1).trim(); } else { return partial.trim(); } } return eventName.trim(); } public static int getYear(String eventKey) { if (!EventHelper.validateEventKey(eventKey)) return -1; return Integer.parseInt(eventKey.substring(0, 4)); } public static int getYearWeek(Date date) { if (date == null) return -1; Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(date.getTime()); return cal.get(Calendar.WEEK_OF_YEAR); } public static int competitionWeek(Date date) { if (date == null) return -1; Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.DAY_OF_YEAR, -1); int week = getYearWeek(cal.getTime()) - Utilities.getFirstCompWeek(cal.get(Calendar.YEAR)); return week < 0 ? 0 : week; } public static String generateLabelForEvent(Event e) { switch (e.getEventTypeEnum()) { case CMP_DIVISION: case CMP_FINALS: if (e.getYear() >= 2017 && e.getCity() != null) { // #2Champs return String.format(CITY_CHAMPIONSHIP_LABEL, e.getCity()); } else { return CHAMPIONSHIP_LABEL; } case REGIONAL: case DISTRICT: case DISTRICT_CMP: case DCMP_DIVISION: /** * Special cases for 2016: * Week 1 is actually Week 0.5, everything else is one less * See http://www.usfirst.org/roboticsprograms/frc/blog-The-Palmetto-Regional */ @Nullable Integer week = e.getWeek(); if (e.getYear() == 2016) { if (week == null) { return String.format(Locale.US, REGIONAL_LABEL, 0); } else if ("2016scmb".equals(e.getKey())) { return String.format(Locale.US, FLOAT_REGIONAL_LABEL, 0.5); } else { return String.format(Locale.US, REGIONAL_LABEL, week - 1); } } else if (week != null){ return String.format(Locale.US, REGIONAL_LABEL, week); } else { return String.format(Locale.US, REGIONAL_LABEL, 0); } case OFFSEASON: String month = ThreadSafeFormatters.renderEventMonth(e.getFormattedStartDate()); return String.format(OFFSEASON_LABEL, month); case PRESEASON: return PRESEASON_LABEL; default: return WEEKLESS_LABEL; } } public static String weekLabelFromNum(int year, @Nullable Integer weekNum) { if (weekNum == null) { return WEEKLESS_LABEL; } if (weekNum <= 0) { return PRESEASON_LABEL; } //let's find the week of CMP and base everything else off that //there should always be something in the CMP set for every year int cmpWeek = Utilities.getCmpWeek(year); /** * Special cases for 2016: * Week 1 is actually Week 0.5, everything else is one less * See http://www.usfirst.org/roboticsprograms/frc/blog-The-Palmetto-Regional */ if (year == 2016 && weekNum == 1) { return String.format(FLOAT_REGIONAL_LABEL, 0.5); } if (year == 2016 && weekNum > 1 && weekNum < cmpWeek) { weekNum--; } if (weekNum > 0 && weekNum < cmpWeek) { return String.format(REGIONAL_LABEL, weekNum); } if (weekNum == cmpWeek) { return CHAMPIONSHIP_LABEL; } if (weekNum > cmpWeek) { return OFFSEASON_LABEL; } return WEEKLESS_LABEL; } public static int weekNumFromLabel(int year, String label) { for (int i = 0; i < 20; i++) { if (weekLabelFromNum(year, i).equals(label)) { return i; } } return -1; } /** * Returns a list of events sorted by start date and type. This is optimal for viewing a team's * season schedule. * * @param events a list of events to render * @param output list to render events into */ public static void renderEventListForTeam(Context context,List<Event> events, List<Object> output) { renderEventListWithComparator(context, events, output, new EventSortByTypeAndDateComparator()); } /** * Returns a list of events sorted by name and type. This is optimal for quickly finding a * particular event within a given week. * * @param events a list of events to render * @param output list to render events into */ public static void renderEventListForWeek(Context context, List<Event> events, List<Object> output) { renderEventListWithComparator(context, events, output, new EventSortByTypeAndDateComparator()); } private static void renderEventListWithComparator( Context context, List<Event> events, List<Object> output, Comparator<Event> comparator) { Collections.sort(events, comparator); EventType lastType = null, currentType; int lastDistrict = -1, currentDistrict; for (Event event : events) { currentType = event.getEventTypeEnum(); currentDistrict = event.getEventDistrictEnum() != null ? event.getEventDistrictEnum().ordinal() : -1; if (currentType != lastType || (currentType == EventType.DISTRICT && currentDistrict != lastDistrict)) { String headerTitle; if (currentType == EventType.DISTRICT) { headerTitle = context.getString(R.string.district_events_header, event.getEventDistrictString()); } else { headerTitle = context.getString(currentType.getCategoryName()); } output.add(new ListSectionHeaderViewModel(headerTitle)); } output.add(event.renderToViewModel(context, Event.RENDER_BASIC)); if (event.isHappeningNow()) { //send out that there are live matches happening for other things to pick up TbaLogger.d("Sending live event broadcast: " + event.getKey()); EventBus.getDefault().post(new LiveEventUpdateEvent(event)); } lastType = currentType; lastDistrict = currentDistrict; } } public static void renderEventListForDistrict( Context context, List<Event> events, List<Object> output) { Collections.sort(events, new EventSortByDateComparator()); String lastHeader = null, currentHeader; for (Event event : events) { currentHeader = weekLabelFromNum(event.getYear(), event.getWeek()); if (!currentHeader.equals(lastHeader)) { String headerTitle = context.getString(R.string.week_events_header, currentHeader); output.add(new ListSectionHeaderViewModel(headerTitle)); } output.add(event.renderToViewModel(context, Event.RENDER_BASIC)); if (event.isHappeningNow()) { //send out that there are live matches happening for other things to pick up TbaLogger.d("Sending live event broadcast: " + event.getKey()); EventBus.getDefault().post(new LiveEventUpdateEvent(event)); } lastHeader = currentHeader; } } public static String getDateString(Date startDate, Date endDate) { if (startDate == null || endDate == null) return ""; if (startDate.equals(endDate)) { return ThreadSafeFormatters.renderEventDate(startDate); } return ThreadSafeFormatters.renderEventShortFormat(startDate) + " to " + ThreadSafeFormatters.renderEventDate(endDate); } public static String getShortCodeForEventKey(String eventKey) { if (validateEventKey(eventKey)) { return eventKey.replaceAll("[0-9]+", ""); } else { return eventKey; } } /** * Returns an abbreviated event or district code like "CALB" from a match key like * "2014calb_qm17" or event key like "2014necmp" or district key like "2014pnw". Returns "" if * the argument doesn't parse as containing an event/district code. */ public static String getEventCode(String matchOrEventOrDistrictKey) { Matcher m = EVENT_KEY_PATTERN.matcher(matchOrEventOrDistrictKey); return m.find() ? m.group().toUpperCase(Locale.US) : ""; } }