code: /*
   * Computoser is a music-composition algorithm and a website to present the results
   * Copyright (C) 2012-2014  Bozhidar Bozhanov
   *
   * Computoser is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * Computoser is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with Computoser.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package com.music.websocket;
  
  import static org.mockito.Matchers.anyLong;
  import static org.mockito.Mockito.doAnswer;
  import static org.mockito.Mockito.mock;
  import static org.mockito.Mockito.when;
  
  import java.io.IOException;
  import java.util.Deque;
  import java.util.LinkedList;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  
  import org.junit.Assert;
  import org.junit.Before;
  import org.junit.Test;
  import org.mockito.Mock;
  import org.mockito.Mockito;
  import org.mockito.MockitoAnnotations;
  import org.mockito.invocation.InvocationOnMock;
  import org.mockito.stubbing.Answer;
  import org.springframework.web.socket.TextMessage;
  import org.springframework.web.socket.WebSocketMessage;
  import org.springframework.web.socket.WebSocketSession;
  
  import com.fasterxml.jackson.core.JsonProcessingException;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.music.model.persistent.Piece;
  import com.music.model.prefs.UserPreferences;
  import com.music.service.PieceService;
  import com.music.web.websocket.Game;
  import com.music.web.websocket.GameHandler;
  import com.music.web.websocket.dto.GameAction;
  import com.music.web.websocket.dto.GameEvent;
  import com.music.web.websocket.dto.GameEventType;
  import com.music.web.websocket.dto.GameMessage;
  
  public class GameTest {
  
      private ObjectMapper mapper = new ObjectMapper();
  
      @Mock
      private PieceService pieceServiceMock;
  
      private Piece piece;
      private Map<String, Deque<WebSocketMessage<?>>> messages = new ConcurrentHashMap<>();
  
      @Before
      public void init() {
          MockitoAnnotations.initMocks(this);
          piece = new Piece();
          piece.setId(1L);
          piece.setTempo(60);
          piece.setMetreNumerator(4);
          piece.setMetreDenominator(8);
          piece.setMainInstrument(1);
          when(pieceServiceMock.getPiece(anyLong())).thenReturn(piece);
          when(pieceServiceMock.getNextPieceId(Mockito.<UserPreferences>any())).thenReturn(piece.getId());
      }
  
      private WebSocketSession getSession(final String id) {
          messages.put(id, new LinkedList<WebSocketMessage<?>>());
          WebSocketSession session = mock(WebSocketSession.class);
          try {
              doAnswer(new Answer<Void>() {
                  @Override
                  public Void answer(InvocationOnMock invocation) throws Throwable {
                      messages.get(id).offer((WebSocketMessage<?>) invocation.getArguments()[0]);
                      return null;
                  }
              }).when(session).sendMessage(Mockito.<WebSocketMessage<?>>any());
          } catch (IOException e) {
              throw new IllegalStateException(e);
          }
          when(session.getId()).thenReturn(id);
          return session;
      }
  
      @Test
      public void gameInitializedTest() throws Exception {
          GameHandler handler = new GameHandler();
          initializeGame(handler);
  
          GameEvent response = mapper.readValue(messages.get("1").removeLast().getPayload().toString(), GameEvent.class);
          Assert.assertEquals(GameEventType.GAME_INITIALIZED, response.getType());
  
          Assert.assertEquals(1, handler.getGames().size());
  
          Game game = handler.getGames().values().iterator().next();
          Assert.assertEquals(game.getId(), response.getGameId());
      }
  
      private void initializeGame(GameHandler handler) {
          try {
              GameMessage msg = new GameMessage();
              msg.setAction(GameAction.INITIALIZE);
              msg.setPlayerName("Game starter");
              TextMessage message = getTextMessage(msg);
              WebSocketSession session = getSession("1");
  
              handler.afterConnectionEstablished(session);
              handler.handleMessage(session, message);
          } catch (Exception ex) {
              throw new IllegalStateException(ex);
          }
      }
  
      private TextMessage getTextMessage(GameMessage msg) {
          String payload;
          try {
              payload = mapper.writeValueAsString(msg);
          } catch (JsonProcessingException e) {
              throw new IllegalStateException(e);
          }
          TextMessage message = new TextMessage(payload);
          return message;
      }
  
      @Test
      public void gameStartedTest() throws Exception {
  
          GameHandler handler = new GameHandler();
          handler.setPieceService(pieceServiceMock);
  
          initializeGame(handler);
          Game game = handler.getGames().values().iterator().next();
  
          GameMessage startMsg = new GameMessage();
          startMsg.setAction(GameAction.START);
          startMsg.setGameId(game.getId());
  
          WebSocketSession session = getSession("1");
          handler.handleMessage(session, getTextMessage(startMsg));
  
          GameEvent responseNewPiece = mapper.readValue(messages.get("1").removeLast().getPayload().toString(), GameEvent.class);
          GameEvent responseGameStarted = mapper.readValue(messages.get("1").removeLast().getPayload().toString(), GameEvent.class);
          Assert.assertEquals(GameEventType.GAME_STARTED, responseGameStarted.getType());
  
          Assert.assertEquals(GameEventType.NEW_PIECE, responseNewPiece.getType());
          Assert.assertEquals(piece.getId(), responseNewPiece.getPieceId());
      }
  
      @Test
      public void playerJoinedTest() throws Exception {
  
          GameHandler handler = new GameHandler();
          handler.setPieceService(pieceServiceMock);
  
          initializeGame(handler);
          Game game = handler.getGames().values().iterator().next();
  
          GameMessage playerJoinMsg = new GameMessage();
          playerJoinMsg.setAction(GameAction.JOIN);
          playerJoinMsg.setPlayerName("AAA");
          playerJoinMsg.setGameId(game.getId());
  
          getSession("1");
          WebSocketSession player2Session = getSession("2");
  
          handler.afterConnectionEstablished(player2Session);
  
          handler.handleMessage(player2Session, getTextMessage(playerJoinMsg));
  
          GameEvent playerJoinedEvent = mapper.readValue(messages.get("1").removeLast().getPayload().toString(), GameEvent.class);
          Assert.assertEquals(GameEventType.PLAYER_JOINED, playerJoinedEvent.getType());
          Assert.assertEquals(playerJoinedEvent.getPlayerId(), "2");
  
          Assert.assertTrue(messages.get("2").size() == 1); // player received a 'player_joined' event for each other player in the game
      }
  
      @Test
      public void derivedMetreTest() {
          Game game = new Game("1", null);
          Piece piece = new Piece();
          piece.setMetreNumerator(4);
          piece.setMetreDenominator(4);
          Assert.assertFalse(game.isNotDerivedMetre(piece, new int[] {8, 8}));
          Assert.assertTrue(game.isNotDerivedMetre(piece, new int[] {4, 8}));
  
          piece.setMetreNumerator(8);
          piece.setMetreDenominator(8);
          Assert.assertFalse(game.isNotDerivedMetre(piece, new int[] {4, 4}));
          Assert.assertTrue(game.isNotDerivedMetre(piece, new int[] {2, 4}));
  
          piece.setMetreNumerator(2);
          piece.setMetreDenominator(4);
          Assert.assertFalse(game.isNotDerivedMetre(piece, new int[] {4, 8}));
          Assert.assertFalse(game.isNotDerivedMetre(piece, new int[] {2, 4}));
          Assert.assertTrue(game.isNotDerivedMetre(piece, new int[] {4, 4}));
  
      }
  
      @Test
      public void gameFinishedTest() throws Exception {
          GameHandler handler = new GameHandler();
          handler.setPieceService(pieceServiceMock);
  
          initializeGame(handler);
          Game game = handler.getGames().values().iterator().next();
  
  
          GameMessage playerJoinMsg = new GameMessage();
          playerJoinMsg.setAction(GameAction.JOIN);
          playerJoinMsg.setPlayerName("AAA");
          playerJoinMsg.setGameId(game.getId());
          WebSocketSession session = getSession("1");
  
          handler.handleMessage(session, getTextMessage(playerJoinMsg));
          game.setSecondsBeforeNextPiece(0);
  
          // Answer all three questions
          for (int i = 0; i < 3; i ++) {
              game.sendNextPiece();
              GameMessage answerMsg = new GameMessage();
              answerMsg.setAction(GameAction.ANSWER);
              com.music.web.websocket.dto.Answer answer = new com.music.web.websocket.dto.Answer();
              answer.setMainInstrument(0);
              answer.setMetreNumerator(2);
              answer.setMetreDenominator(4);
              answer.setTempo(80);
              answerMsg.setAnswer(answer);
              answerMsg.setGameId(game.getId());
              handler.handleMessage(session, getTextMessage(answerMsg));
          }
          Assert.assertFalse(game.isStarted());
          Assert.assertTrue(game.getResults().getRanking().contains("1"));
          Assert.assertTrue(game.getResults().getScores().containsKey(playerJoinMsg.getPlayerName()));
      }
      //TODO two async players test, automatic new piece test, timeout new piece test
  
  }
  