/*
* Copyright (c) 2015 Shervin Asgari
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package no.asgari.civilization.server.resource;
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.html.HtmlEscapers;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import io.dropwizard.auth.Auth;
import lombok.Cleanup;
import lombok.extern.log4j.Log4j;
import no.asgari.civilization.server.action.GameAction;
import no.asgari.civilization.server.action.GameLogAction;
import no.asgari.civilization.server.action.PlayerAction;
import no.asgari.civilization.server.action.TurnAction;
import no.asgari.civilization.server.action.UndoAction;
import no.asgari.civilization.server.dto.ChatDTO;
import no.asgari.civilization.server.dto.CheckNameDTO;
import no.asgari.civilization.server.dto.CivHighscoreDTO;
import no.asgari.civilization.server.dto.CreateNewGameDTO;
import no.asgari.civilization.server.dto.DrawDTO;
import no.asgari.civilization.server.dto.GameDTO;
import no.asgari.civilization.server.dto.GameLogDTO;
import no.asgari.civilization.server.dto.MessageDTO;
import no.asgari.civilization.server.dto.PbfDTO;
import no.asgari.civilization.server.dto.PlayerDTO;
import no.asgari.civilization.server.dto.WinnerDTO;
import no.asgari.civilization.server.model.Chat;
import no.asgari.civilization.server.model.GameLog;
import no.asgari.civilization.server.model.PBF;
import no.asgari.civilization.server.model.Player;
import no.asgari.civilization.server.model.PlayerTurn;
import no.asgari.civilization.server.model.Tech;
import org.hibernate.validator.constraints.NotEmpty;
import org.mongojack.DBCursor;
import org.mongojack.DBQuery;
import org.mongojack.JacksonDBCollection;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static java.util.stream.Collectors.toList;
@Path("game")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Log4j
public class GameResource {
private final DB db;
@Context
private UriInfo uriInfo;
private final JacksonDBCollection<PBF, String> pbfCollection;
public GameResource(DB db) {
this.db = db;
this.pbfCollection = JacksonDBCollection.wrap(db.getCollection(PBF.COL_NAME), PBF.class, String.class);
}
/**
* This is the default method for this resource.
* It will return all active games
*
* @return
*/
@GET
@Timed
public Response getAllGames() {
GameAction gameAction = new GameAction(db);
List<PbfDTO> games = gameAction.getAllGames();
return Response.ok()
.entity(games)
.build();
}
/**
* Returns a specific game
*
* @return
*/
@Path("/{pbfId}")
@GET
@Timed
public Response getGame(@Auth(required = false) Player player, @PathParam("pbfId") String pbfId) {
if (Strings.isNullOrEmpty(pbfId)) {
log.error("pbfId is missing");
return Response.status(Response.Status.NOT_FOUND).build();
}
GameAction gameAction = new GameAction(db);
PBF pbf = gameAction.findPBFById(pbfId);
GameDTO gameDTO = gameAction.mapGameDTO(pbf, player);
return Response.ok()
.entity(gameDTO)
.build();
}
/**
* Will return a collection of all pbf ids
*/
@Path("/player")
@GET
@Timed
public Response getGamesByPlayer(@Auth Player player) {
PlayerAction playerAction = new PlayerAction(db);
Set<String> games = playerAction.getGames(player);
return Response.ok().entity(games).build();
}
/**
* Will return a list of all the players of this PBF.
* Handy for selecting players whom to trade with. Will remove the current user from the list
*/
@GET
@Path("/{pbfId}/players")
public Response getAllPlayersForPBF(@NotEmpty @PathParam("pbfId") String pbfId, @Auth(required = false) Player player) {
GameAction gameAction = new GameAction(db);
List<PlayerDTO> players = gameAction.getAllPlayers(pbfId);
if (player != null) {
return Response.ok()
.entity(players.stream().filter(p -> !p.getPlayerId().equals(player.getId())).collect(toList()))
.build();
}
return Response.ok()
.entity(players)
.build();
}
/**
* Will return a list of all the players of this PBF.
*/
@GET
@Path("/{pbfId}/players/all")
public Response getAllPlayersForPBF(@NotEmpty @PathParam("pbfId") String pbfId) {
GameAction gameAction = new GameAction(db);
List<PlayerDTO> players = gameAction.getAllPlayers(pbfId);
return Response.ok()
.entity(players)
.build();
}
@POST
@Timed
public Response createGame(@Valid CreateNewGameDTO dto, @Auth Player player) {
Preconditions.checkNotNull(dto);
Preconditions.checkNotNull(player);
log.info("Creating game " + dto);
GameAction gameAction = new GameAction(db);
String id = gameAction.createNewGame(dto, player.getId());
URI location = URI.create("/" + id);
log.debug("location for new game is " + location);
return Response.status(Response.Status.CREATED)
.location(location)
.build();
}
@DELETE
@Timed
@Path("/{pbfId}/end")
public void endGame(@PathParam("pbfId") String pbfId, @Auth Player player, @QueryParam("winner") String winner) {
Preconditions.checkNotNull(pbfId);
log.info("Ending game with id " + pbfId);
GameAction gameAction = new GameAction(db);
gameAction.endGame(pbfId, player, winner);
}
@POST
@Timed
@Path("/{pbfId}/join")
public Response joinGame(@NotEmpty @PathParam("pbfId") String pbfId, @Auth Player player) {
Preconditions.checkNotNull(pbfId);
Preconditions.checkNotNull(player);
GameAction gameAction = new GameAction(db);
gameAction.joinGame(pbfId, player, Optional.empty());
return Response.noContent().build();
}
/**
* Withdraw from existing game.
* Game must not have started
*/
@POST
@Timed
@Path("/{pbfId}/withdraw")
public Response withdrawFromGame(@NotEmpty @PathParam("pbfId") String pbfId, @Auth Player player) {
Preconditions.checkNotNull(pbfId);
Preconditions.checkNotNull(player);
GameAction gameAction = new GameAction(db);
boolean ok = gameAction.withdrawFromGame(pbfId, player.getId());
if (ok) {
return Response.noContent().build();
}
return Response.status(Response.Status.NOT_ACCEPTABLE).build();
}
/**
* Gets all the available techs. Will remove the techs that player already have chosen
*
* @param pbfId - The PBF
* @param player - The Authenticated player
* @return - Response ok with a list of techs
*/
@GET
@Timed
@Path("/{pbfId}/techs")
public List<Tech> getAvailableTechs(@NotEmpty @PathParam("pbfId") String pbfId, @Auth Player player) {
return new PlayerAction(db).getRemaingTechsForPlayer(player.getId(), pbfId);
}
@GET
@Timed
@Path("/{pbfId}/publiclog")
public List<GameLogDTO> getPublicLog(@NotEmpty @PathParam("pbfId") String pbfId) {
GameLogAction gameLogAction = new GameLogAction(db);
List<GameLog> allPublicLogs = gameLogAction.getGameLogs(pbfId);
List<GameLogDTO> gameLogDTOs = new ArrayList<>();
if (!allPublicLogs.isEmpty()) {
gameLogDTOs = allPublicLogs.stream()
.filter(log -> !Strings.isNullOrEmpty(log.getPublicLog()))
.map(gl -> new GameLogDTO(gl.getId(), gl.getPublicLog(), gl.getCreatedInMillis(), new DrawDTO(gl.getDraw())))
.collect(toList());
}
return gameLogDTOs;
}
@GET
@Timed
@Path("/{pbfId}/privatelog")
public List<GameLogDTO> getPrivateLog(@NotEmpty @PathParam("pbfId") String pbfId, @Auth Player player) {
GameLogAction gameLogAction = new GameLogAction(db);
List<GameLog> allPrivateLogs = gameLogAction.getGameLogsBelongingToPlayer(pbfId, player.getUsername());
List<GameLogDTO> gameLogDTOs = new ArrayList<>();
if (!allPrivateLogs.isEmpty()) {
gameLogDTOs = allPrivateLogs.stream()
.filter(log -> !Strings.isNullOrEmpty(log.getPrivateLog()))
.map(gl -> new GameLogDTO(gl.getId(), gl.getPrivateLog(), gl.getCreatedInMillis(), new DrawDTO(gl.getDraw())))
.collect(toList());
}
return gameLogDTOs;
}
/**
* Returns a list of all undoes that are currently initiated and still not finished
*
* @param pbfId
* @return
*/
@GET
@Path("/{pbfId}/undo/active")
@Timed
public Response getAllActiveUndosCurrentlyInProgress(@NotEmpty @PathParam("pbfId") String pbfId) {
UndoAction undoAction = new UndoAction(db);
List<GameLog> gamelogs = undoAction.getAllActiveUndos(pbfId);
return Response.ok().entity(gamelogs).build();
}
/**
* Returns a list of all undoes that are finished voted
*
* @param pbfId
* @return
*/
@GET
@Path("/{pbfId}/undo/finished")
@Timed
public Response getAllFinishedUndos(@NotEmpty @PathParam("pbfId") String pbfId) {
UndoAction undoAction = new UndoAction(db);
List<GameLog> gamelogs = undoAction.getAllFinishedUndos(pbfId);
return Response.ok().entity(gamelogs).build();
}
/**
* Initiates undo for an item.
* <p>
* Will throw BAD_REQUEST if undo has already been performed
*
* @param player
* @param pbfId
* @param gameLogId
* @return 200 ok
*/
@PUT
@Path("/{pbfId}/undo/{gameLogId}")
@Timed
public Response undoItem(@Auth Player player, @NotEmpty @PathParam("pbfId") String pbfId, @NotEmpty @PathParam("gameLogId") String gameLogId) {
GameLogAction gameLogAction = new GameLogAction(db);
GameLog gameLog = gameLogAction.findGameLogById(gameLogId);
UndoAction undoAction = new UndoAction(db);
undoAction.initiateUndo(gameLog, player.getId());
return Response.ok().build();
}
/**
* Performs yes vote on an undo
* <p>
* Returns error if no undo is found
*
* @param player
* @param pbfId
* @param gameLogId
* @return 200 ok
*/
@PUT
@Path("/{pbfId}/vote/{gameLogId}/yes")
@Timed
public Response voteYes(@Auth Player player, @NotEmpty @PathParam("pbfId") String pbfId, @NotEmpty @PathParam("gameLogId") String gameLogId) {
GameLogAction gameLogAction = new GameLogAction(db);
GameLog gameLog = gameLogAction.findGameLogById(gameLogId);
if (gameLog.getDraw() == null || gameLog.getDraw().getUndo() == null) {
log.error("There is no undo to vote on");
return Response.status(Response.Status.PRECONDITION_FAILED)
.build();
}
UndoAction undoAction = new UndoAction(db);
undoAction.vote(gameLog, player.getId(), true);
return Response.ok().build();
}
/**
* Performs no vote on an undo
* <p>
* Returns "412 Precondition failed" if no undo is found
*
* @param player
* @param pbfId
* @param gameLogId
* @return 200 ok
*/
@PUT
@Path("/{pbfId}/vote/{gameLogId}/no")
@Timed
public Response voteNo(@Auth Player player, @NotEmpty @PathParam("pbfId") String pbfId, @NotEmpty @PathParam("gameLogId") String gameLogId) {
GameLogAction gameLogAction = new GameLogAction(db);
GameLog gameLog = gameLogAction.findGameLogById(gameLogId);
if (gameLog.getDraw() == null || gameLog.getDraw().getUndo() == null) {
log.error("There is no undo to vote on");
return Response.status(Response.Status.PRECONDITION_FAILED)
.build();
}
UndoAction undoAction = new UndoAction(db);
undoAction.vote(gameLog, player.getId(), false);
return Response.ok().build();
}
@POST
@Timed
@Path("/{pbfId}/chat")
@Consumes(value = MediaType.APPLICATION_FORM_URLENCODED)
@Produces(value = MediaType.APPLICATION_JSON)
public Response chat(@Auth Player player, @FormParam("message") String message, @PathParam("pbfId") String pbfId) {
Preconditions.checkNotNull(message);
GameAction gameAction = new GameAction(db);
Chat chat = gameAction.chat(pbfId, message, player.getUsername());
return Response.created(URI.create(chat.getId())).entity(gameAction.getChat(pbfId)).build();
}
@POST
@Timed
@Path("/publicchat")
@Consumes(value = MediaType.APPLICATION_FORM_URLENCODED)
@Produces(value = MediaType.APPLICATION_JSON)
public Response publicChat(@Auth Player player, @FormParam("message") String message) {
Preconditions.checkNotNull(message);
GameAction gameAction = new GameAction(db);
Chat chat = gameAction.chat(null, message, player.getUsername());
return Response.created(URI.create(chat.getId())).entity(gameAction.getPublicChat()).build();
}
@GET
@Timed
@Path("/{pbfId}/chat")
@Produces(value = MediaType.APPLICATION_JSON)
public Response getChatList(@PathParam("pbfId") String pbfId) {
GameAction gameAction = new GameAction(db);
List<ChatDTO> chats = gameAction.getChat(pbfId);
return Response.ok().entity(chats).build();
}
/**
* Gets public chat which is 1 week old and maximum 50 entries, sorted on created
*/
@GET
@Timed
@Path("/publicchat")
@Produces(value = MediaType.APPLICATION_JSON)
public Response getPublicChatList() {
GameAction gameAction = new GameAction(db);
List<ChatDTO> chats = gameAction.getPublicChat();
return Response.ok().entity(chats).build();
}
@POST
@Timed
@Path("/{pbfId}/map")
@Consumes(value = MediaType.APPLICATION_FORM_URLENCODED)
@Produces(value = MediaType.APPLICATION_JSON)
public Response updateMap(@Auth Player player, @FormParam("link") String link, @PathParam("pbfId") String pbfId) {
Preconditions.checkNotNull(link);
GameAction gameAction = new GameAction(db);
String linkId = gameAction.addMapLink(pbfId, link, player.getId());
return Response.ok().entity(new MessageDTO(linkId)).build();
}
@POST
@Timed
@Path("/{pbfId}/asset")
@Consumes(value = MediaType.APPLICATION_FORM_URLENCODED)
@Produces(value = MediaType.APPLICATION_JSON)
public Response updateAsset(@Auth Player player, @FormParam("link") String link, @PathParam("pbfId") String pbfId) {
Preconditions.checkNotNull(link);
GameAction gameAction = new GameAction(db);
String linkId = gameAction.addAssetLink(pbfId, link, player.getId());
return Response.ok().entity(new MessageDTO(linkId)).build();
}
@GET
@Path("winners")
@Produces(value = MediaType.APPLICATION_JSON)
public List<WinnerDTO> getWinners() {
GameAction gameAction = new GameAction(db);
return gameAction.getWinners();
}
@GET
@Path("civhighscore")
@Produces(value = MediaType.APPLICATION_JSON)
public List<CivHighscoreDTO> getCivHighscore() {
GameAction gameAction = new GameAction(db);
return gameAction.getCivHighscore();
}
@GET
@Path("/{pbfId}/turns")
@Produces(value = MediaType.APPLICATION_JSON)
public List<PlayerTurn> getAllPublicTurns(@PathParam("pbfId") String pbfId) {
TurnAction turnAction = new TurnAction(db);
return turnAction.getAllPublicTurns(pbfId);
}
}