package com.boardgamegeek.model;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.text.format.DateUtils;
import com.boardgamegeek.R;
import com.boardgamegeek.provider.BggContract;
import com.boardgamegeek.util.DateTimeUtils;
import com.boardgamegeek.util.StringUtils;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Path;
import org.simpleframework.xml.Root;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@Root(name = "play")
public class Play {
public static final int QUANTITY_DEFAULT = 1;
public static final int LENGTH_DEFAULT = 0;
private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
@Attribute(name = "id")
public int playId;
@Attribute
private String date;
private long playDate = DateTimeUtils.UNPARSED_DATE;
@Attribute
public int quantity;
@Attribute
public int length;
@Attribute
private int incomplete;
public boolean Incomplete() {
return incomplete == 1;
}
public void setIncomplete(boolean value) {
incomplete = value ? 1 : 0;
}
@Attribute
private int nowinstats;
public boolean NoWinStats() {
return nowinstats == 1;
}
public void setNoWinStats(boolean value) {
nowinstats = value ? 1 : 0;
}
@Attribute
public String location;
@Path("item")
@Attribute(name = "name")
public String gameName;
@Path("item")
@Attribute(name = "objectid")
public int gameId;
@Path("item")
@Attribute
private String objecttype;
@Path("item")
@ElementList
public List<Subtype> subtypes;
@Root(name = "subtype")
public static class Subtype {
@Attribute
public String value;
}
@Element(required = false)
public String comments;
public long syncTimestamp;
public long startTime;
public int playerCount;
public long deleteTimestamp;
public long updateTimestamp;
public long dirtyTimestamp;
@ElementList(required = false)
private List<Player> players;
public Play() {
init(BggContract.INVALID_ID, "");
}
public Play(int gameId, String gameName) {
init(gameId, gameName);
}
private void init(int gameId, String gameName) {
this.gameId = gameId;
this.gameName = gameName;
quantity = QUANTITY_DEFAULT;
length = LENGTH_DEFAULT;
location = "";
comments = "";
startTime = 0;
players = new ArrayList<>();
}
// DATE
/**
* The date of the play in the yyyy-MM-dd format. This is the format the 'Geek uses and how it's stored in the
* Content DB.
*
* @return The formatted date
*/
public String getDate() {
playDate = DateTimeUtils.tryParseDate(playDate, date, FORMAT);
return DateTimeUtils.formatDateForApi(playDate);
}
public long getDateInMillis() {
playDate = DateTimeUtils.tryParseDate(playDate, date, FORMAT);
return playDate;
}
/**
* A text version of the date, formatted for display in the UI.
*
* @return a localized date.
*/
public CharSequence getDateForDisplay(Context context) {
playDate = DateTimeUtils.tryParseDate(playDate, date, FORMAT);
return DateUtils.formatDateTime(context, playDate, DateUtils.FORMAT_SHOW_DATE);
}
public void setDate(int year, int month, int day) {
playDate = DateTimeUtils.UNPARSED_DATE;
date = DateTimeUtils.formatDateForApi(year, month, day);
playDate = DateTimeUtils.tryParseDate(playDate, date, FORMAT);
}
/**
* Sets the play's date
*
* @param date in the yyyy-MM-dd format
*/
public void setDate(String date) {
playDate = DateTimeUtils.UNPARSED_DATE;
this.date = date;
playDate = DateTimeUtils.tryParseDate(playDate, date, FORMAT);
}
public void setCurrentDate() {
final Calendar c = Calendar.getInstance();
setDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
}
// PLAYERS
public List<Player> getPlayers() {
if (players == null) {
return new ArrayList<>();
}
return players;
}
public int getPlayerCount() {
if (players == null) {
return 0;
}
return players.size();
}
public void setPlayers(List<Player> players) {
if (players != null) {
this.players.addAll(players);
}
}
public void clearPlayers() {
if (players != null) {
players.clear();
}
}
public void addPlayer(Player player) {
if (players == null) {
players = new ArrayList<>();
}
// if player has seat, bump down other players
if (!arePlayersCustomSorted() && player.getSeat() != Player.SEAT_UNKNOWN) {
for (int i = players.size(); i >= player.getSeat(); i--) {
Player p = getPlayerAtSeat(i);
if (p != null) {
p.setSeat(i + 1);
}
}
}
players.add(player);
sortPlayers();
}
public void removePlayer(Player player, boolean resort) {
if (players == null || getPlayerCount() == 0) {
return;
}
if (resort && !arePlayersCustomSorted()) {
for (int i = player.getSeat(); i < players.size(); i++) {
Player p = getPlayerAtSeat(i + 1);
if (p != null) {
p.setSeat(i);
}
}
}
players.remove(player);
}
/**
* Replaces a player at the position with a new player. If the position doesn't exists, the player is added instead.
*/
public void replaceOrAddPlayer(Player player, int position) {
if (position >= 0 && position < players.size()) {
players.set(position, player);
} else {
players.add(player);
}
}
public Player getPlayerAtSeat(int seat) {
if (players == null) {
return null;
}
for (Player player : players) {
if (player != null && player.getSeat() == seat) {
return player;
}
}
return null;
}
public boolean reorderPlayers(int fromSeat, int toSeat) {
if (players == null || getPlayerCount() == 0) {
return false;
}
if (arePlayersCustomSorted()) {
return false;
}
Player player = getPlayerAtSeat(fromSeat);
if (player == null) {
return false;
}
player.setSeat(Player.SEAT_UNKNOWN);
try {
if (fromSeat > toSeat) {
for (int i = fromSeat - 1; i >= toSeat; i--) {
getPlayerAtSeat(i).setSeat(i + 1);
}
} else {
for (int i = fromSeat + 1; i <= toSeat; i++) {
getPlayerAtSeat(i).setSeat(i - 1);
}
}
} catch (NullPointerException e) {
return false;
}
player.setSeat(toSeat);
sortPlayers();
return true;
}
/**
* Sets the start player based on the index, keeping the other players in order, assigns seats, then sorts
*
* @param startPlayerIndex The zero-based index of the new start player
*/
public void pickStartPlayer(int startPlayerIndex) {
int playerCount = getPlayerCount();
for (int i = 0; i < playerCount; i++) {
Player p = players.get(i);
p.setSeat((i - startPlayerIndex + playerCount) % playerCount + 1);
}
sortPlayers();
}
/**
* Randomizes the order of players, assigning seats to the new order.
*/
public void randomizePlayerOrder() {
if (players == null || players.size() == 0) {
return;
}
Collections.shuffle(players);
int playerCount = players.size();
for (int i = 0; i < playerCount; i++) {
Player p = players.get(i);
p.setSeat(i + 1);
}
}
/**
* Sort the players by seat; unseated players left unsorted at the bottom of the list.
*/
public void sortPlayers() {
int index = 0;
for (int i = 1; i <= getPlayerCount(); i++) {
Player p = getPlayerAtSeat(i);
if (p != null) {
players.remove(p);
players.add(index, p);
index++;
}
}
}
/**
* Determine if the starting positions indicate the players are custom sorted.
*/
public boolean arePlayersCustomSorted() {
if (!hasStartingPositions()) {
return true;
}
int seat = 1;
do {
boolean foundSeat = false;
for (Player player : players) {
if (player != null && player.getSeat() == seat) {
foundSeat = true;
break;
}
}
if (!foundSeat) {
return true;
}
seat++;
if (seat > getPlayerCount()) {
return false;
}
} while (seat < 100);
return true;
}
/**
* Determine if any player has a starting position.
*/
public boolean hasStartingPositions() {
if (getPlayerCount() == 0) {
return false;
}
for (Player player : players) {
if (player != null && !TextUtils.isEmpty(player.getStartingPosition())) {
return true;
}
}
return false;
}
/**
* Remove the starting position for all players.
*/
public void clearPlayerPositions() {
if (getPlayerCount() == 0) {
return;
}
for (Player player : players) {
if (player != null) {
player.setStartingPosition(null);
}
}
}
// MISC
/**
* Determine if any player has a team/color.
*/
public boolean hasColors() {
if (getPlayerCount() == 0) {
return false;
}
for (Player player : players) {
if (player != null && !TextUtils.isEmpty(player.color)) {
return true;
}
}
return false;
}
public double getHighScore() {
if (getPlayerCount() == 0) {
return 0.0;
}
double highScore = Double.MIN_VALUE;
for (Player player : players) {
if (player == null) continue;
double score = StringUtils.parseDouble(player.score, Double.MIN_VALUE);
if (score > highScore) {
highScore = score;
}
}
return highScore;
}
/**
* Determines if this play appears to have started.
*
* @return true, if it's not ended and the start time has been set.
*/
public boolean hasStarted() {
return length == 0 && startTime > 0;
}
public void start() {
length = 0;
startTime = System.currentTimeMillis();
}
public void end() {
if (startTime > 0) {
length = DateTimeUtils.howManyMinutesOld(startTime);
startTime = 0;
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || o.getClass() != this.getClass()) {
return false;
}
Play p = (Play) o;
boolean eq = (playId == p.playId)
&& (gameId == p.gameId)
&& (playDate == p.playDate)
&& (quantity == p.quantity)
&& (length == p.length)
&& ((location == null && p.location == null) || (location != null && location.equals(p.location)))
&& (incomplete == p.incomplete)
&& (nowinstats == p.nowinstats)
&& ((comments == null && p.comments == null) || (comments != null && comments.equals(p.comments)))
&& (startTime == p.startTime)
&& ((players == null && p.players == null) || (players != null && p.players != null && players.size() == p.players
.size()));
if (eq && players != null) {
for (int i = 0; i < players.size(); i++) {
if (!players.get(i).equals(p.getPlayers().get(i))) {
return false;
}
}
}
return eq;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + playId;
result = prime * result + gameId;
result = prime * result + ((gameName == null) ? 0 : gameName.hashCode());
result = prime * result + (int) (playDate ^ (playDate >>> 32));
result = prime * result + quantity;
result = prime * result + length;
result = prime * result + ((location == null) ? 0 : location.hashCode());
result = prime * result + incomplete;
result = prime * result + nowinstats;
result = prime * result + ((comments == null) ? 0 : comments.hashCode());
long u = Double.doubleToLongBits(syncTimestamp);
result = prime * result + (int) (u ^ (u >>> 32));
long t = Double.doubleToLongBits(startTime);
result = prime * result + (int) (t ^ (t >>> 32));
return result;
}
public String toShortDescription(Context context) {
Resources r = context.getResources();
return r.getString(R.string.play_description_game_segment, gameName) +
r.getString(R.string.play_description_date_segment, getDate());
}
public String toLongDescription(Context context) {
Resources resources = context.getResources();
StringBuilder sb = new StringBuilder();
toLongDescriptionPrefix(context, sb);
if (players.size() > 0) {
sb.append(" ").append(resources.getString(R.string.with));
if (arePlayersCustomSorted()) {
for (Player player : players) {
if (player != null) {
sb.append("\n").append(player.toLongDescription(context));
}
}
} else {
for (int i = 0; i < players.size(); i++) {
Player player = getPlayerAtSeat(i + 1);
if (player != null) {
sb.append("\n").append(player.toLongDescription(context));
}
}
}
}
if (!TextUtils.isEmpty(comments)) {
sb.append("\n").append(comments);
}
if (playId > 0) {
sb.append("\n").append(resources.getString(R.string.play_description_play_url_segment, String.valueOf(playId)).trim());
} else {
sb.append("\n").append(resources.getString(R.string.play_description_game_url_segment, String.valueOf(gameId)).trim());
}
return sb.toString();
}
private void toLongDescriptionPrefix(Context context, StringBuilder sb) {
Resources resources = context.getResources();
sb.append(resources.getString(R.string.play_description_game_segment, gameName));
if (quantity > 1) {
sb.append(resources.getQuantityString(R.plurals.play_description_quantity_segment, quantity, quantity));
}
if (length > 0) {
sb.append(resources.getString(R.string.play_description_length_segment, DateTimeUtils.describeMinutes(context, length)));
}
sb.append(resources.getString(R.string.play_description_date_segment, getDate()));
if (!TextUtils.isEmpty(location)) {
sb.append(resources.getString(R.string.play_description_location_segment, location));
}
}
}