package com.schneeloch.bostonbusmap_library.data; import java.text.DateFormat; import java.util.Date; import com.google.common.base.Objects; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableMap; import com.schneeloch.bostonbusmap_library.transit.TransitSystem; import com.schneeloch.bostonbusmap_library.util.MoreInfoConstants; import com.schneeloch.bostonbusmap_library.util.StringUtil; import android.os.Parcel; import android.os.Parcelable; import android.text.Html; import android.text.Spanned; /** * A container of a prediction. Immutable * * @author schneg * */ public class TimePrediction implements IPrediction { public static final int NULL_LATENESS = -1; /** * This may be null */ protected final String vehicleId; protected final String direction; protected final String routeName; protected final String routeTitle; protected final long arrivalTimeMillis; protected final boolean affectedByLayover; protected final boolean isDelayed; /** * In seconds */ protected final int lateness; protected final String block; protected final String stopId; public TimePrediction(long arrivalTimeMillis, String vehicleId, String direction, String routeName, String routeTitle, boolean affectedByLayover, boolean isDelayed, int lateness, String block, String stopId) { this.vehicleId = vehicleId; this.direction = direction; this.routeName = routeName; this.routeTitle = routeTitle; this.arrivalTimeMillis = arrivalTimeMillis; this.affectedByLayover = affectedByLayover; this.isDelayed = isDelayed; this.lateness = lateness; this.block = block; this.stopId = stopId; } public void makeSnippet(StringBuilder builder, boolean showRunNumber) { int minutes = getMinutes(); if (minutes < 0) { return; } builder.append("Route <b>").append(routeTitle).append("</b>"); if (vehicleId != null) { builder.append(", Vehicle <b>").append(vehicleId).append("</b>"); } if (!StringUtil.isEmpty(block) && showRunNumber) { builder.append("<br />Run number <b>").append(block).append("</b>"); } if (direction != null) { builder.append("<br />").append(direction); } if (isDelayed) { builder.append("<br /><b>Delayed</b>"); } if (affectedByLayover) { //hmm... } if (minutes == 0) { builder.append("<br />Arriving <b>now</b>!"); } else { DateFormat dateFormat = TransitSystem.getDefaultTimeFormat(); Date date = new Date(arrivalTimeMillis); if (dateFormat != null) { //the vast majority of the time this should be true but someone reported an exception where it's not String formatted = dateFormat.format(date); builder.append("<br />Arriving in <b>").append(minutes); builder.append(" min</b> at ").append(formatted.trim()); } else { builder.append("<br />Arriving in <b>").append(minutes).append(" min</b>"); } } } @Override public int compareTo(IPrediction anotherObj) { if (anotherObj instanceof TimePrediction) { TimePrediction another = (TimePrediction)anotherObj; ComparisonChain comparisonChain = ComparisonChain.start().compare(arrivalTimeMillis, another.arrivalTimeMillis) .compare(direction, another.direction); if (routeName != null) { comparisonChain.compare(routeName, another.routeName); } return comparisonChain .result(); } else { throw new RuntimeException("Cannot compare distance prediction with time prediction"); } } @Override public int hashCode() { return Objects.hashCode(vehicleId, direction, routeName, arrivalTimeMillis, affectedByLayover, isDelayed, lateness, block, stopId); } @Override public boolean equals(Object o) { if (o instanceof TimePrediction) { TimePrediction another = (TimePrediction)o; return Objects.equal(arrivalTimeMillis, another.arrivalTimeMillis) && Objects.equal(vehicleId, another.vehicleId) && Objects.equal(direction, another.direction) && Objects.equal(routeName, another.routeName) && Objects.equal(affectedByLayover, another.affectedByLayover) && Objects.equal(isDelayed, another.isDelayed) && Objects.equal(lateness, another.lateness) && Objects.equal(block, another.block) && Objects.equal(stopId, another.stopId); } else { return false; } } public String getRouteName() { return routeName; } public String getRouteTitle() { return routeTitle; } public static int calcMinutes(long arrivalTimeMillis) { return (int)(arrivalTimeMillis - System.currentTimeMillis()) / 1000 / 60; } public int getMinutes() { return calcMinutes(arrivalTimeMillis) + (lateness / 60); } public String getVehicleId() { return vehicleId; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(arrivalTimeMillis); dest.writeString(vehicleId != null ? vehicleId : ""); dest.writeString(direction); dest.writeString(routeName); dest.writeString(routeTitle); writeBoolean(dest, affectedByLayover); writeBoolean(dest, isDelayed); dest.writeInt(lateness); dest.writeString(block); dest.writeString(stopId); } public static final Parcelable.Creator<TimePrediction> CREATOR = new Creator<TimePrediction>() { @Override public TimePrediction[] newArray(int size) { return new TimePrediction[size]; } @Override public TimePrediction createFromParcel(Parcel source) { //NOTE: if this changes you must also change CommuterRailPrediction.CREATOR.createFromParcel long arrivalTimeMillis = source.readLong(); String vehicleId = source.readString(); if (vehicleId.length() == 0) { vehicleId = null; } String direction = source.readString(); String routeName = source.readString(); String routeTitle = source.readString(); boolean affectedByLayover = readBoolean(source); boolean isDelayed = readBoolean(source); int lateness = source.readInt(); String block = source.readString(); String stopId = source.readString(); TimePrediction prediction = new TimePrediction(arrivalTimeMillis, vehicleId, direction, routeName, routeTitle, affectedByLayover, isDelayed, lateness, block, stopId); return prediction; } }; public ImmutableMap<String, Spanned> makeSnippetMap() { StringBuilder ret = new StringBuilder(); makeSnippet(ret, TransitSystem.showRunNumber()); ImmutableMap<String, Spanned> map = ImmutableMap.of(MoreInfoConstants.textKey, Html.fromHtml(ret.toString())); return map; } protected static boolean readBoolean(Parcel source) { return source.readInt() == 1; } protected static void writeBoolean(Parcel dest, boolean data) { dest.writeInt(data ? 1 : 0); } @Override public boolean isInvalid() { return getMinutes() < 0; } public void addVehicleAndRoute(StringBuilder builder) { builder.append("Route <b>").append(routeTitle).append("</b>"); if (vehicleId != null) { builder.append(", Vehicle <b>").append(vehicleId).append("</b>"); } } }