package games.strategy.engine.lobby.client.ui;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;

import games.strategy.engine.lobby.server.GameDescription;
import games.strategy.engine.lobby.server.ILobbyGameBroadcaster;
import games.strategy.engine.lobby.server.ILobbyGameController;
import games.strategy.engine.message.IChannelMessenger;
import games.strategy.engine.message.IRemoteMessenger;
import games.strategy.engine.message.MessageContext;
import games.strategy.net.GUID;
import games.strategy.net.IMessenger;
import games.strategy.util.Tuple;

class LobbyGameTableModel extends AbstractTableModel {
  private static final long serialVersionUID = 6399458368730633993L;

  enum Column {
    Host, Name, GV, Round, Players, P, B, EV, Started, Status, Comments, GUID
  }

  private final IMessenger m_messenger;

  // these must only be accessed in the swing event thread
  private final List<Tuple<GUID, GameDescription>> gameList;
  private final ILobbyGameBroadcaster lobbyGameBroadcaster;

  LobbyGameTableModel(final IMessenger messenger, final IChannelMessenger channelMessenger,
      final IRemoteMessenger remoteMessenger) {

    gameList = new ArrayList<>();

    m_messenger = messenger;
    lobbyGameBroadcaster = new ILobbyGameBroadcaster() {
      @Override
      public void gameUpdated(final GUID gameId, final GameDescription description) {
        assertSentFromServer();
        updateGame(gameId, description);
      }

      @Override
      public void gameRemoved(final GUID gameId) {
        assertSentFromServer();
        removeGame(gameId);
      }
    };
    channelMessenger.registerChannelSubscriber(lobbyGameBroadcaster, ILobbyGameBroadcaster.GAME_BROADCASTER_CHANNEL);

    final Map<GUID, GameDescription> games =
        ((ILobbyGameController) remoteMessenger.getRemote(ILobbyGameController.GAME_CONTROLLER_REMOTE)).listGames();
    for (final GUID id : games.keySet()) {
      updateGame(id, games.get(id));
    }
  }

  private void removeGame(final GUID gameId) {
    SwingUtilities.invokeLater(() -> {
      if (gameId == null) {
        return;
      }

      final Tuple<GUID, GameDescription> gameToRemove = findGame(gameId);
      if (gameToRemove != null) {
        final int index = gameList.indexOf(gameToRemove);
        gameList.remove(gameToRemove);
        fireTableRowsDeleted(index, index);
      }
    });
  }

  private Tuple<GUID, GameDescription> findGame(final GUID gameId) {
    for (final Tuple<GUID, GameDescription> game : gameList) {
      if (game.getFirst().equals(gameId)) {
        return game;
      }
    }
    return null;
  }


  protected ILobbyGameBroadcaster getLobbyGameBroadcaster() {
    return lobbyGameBroadcaster;
  }

  GameDescription get(final int i) {
    return gameList.get(i).getSecond();
  }

  @Override
  public Class<?> getColumnClass(final int columnIndex) {
    if (columnIndex == getColumnIndex(Column.Started)) {
      return Instant.class;
    }
    return Object.class;
  }

  private void assertSentFromServer() {
    if (!MessageContext.getSender().equals(m_messenger.getServerNode())) {
      throw new IllegalStateException("Invalid sender");
    }
  }

  private void updateGame(final GUID gameId, final GameDescription description) {
    SwingUtilities.invokeLater(() -> {
      if (gameId == null) {
        return;
      }

      final Tuple<GUID, GameDescription> toReplace = findGame(gameId);
      if (toReplace == null) {
        gameList.add(Tuple.of(gameId, description));
      } else {
        final int replaceIndex = gameList.indexOf(toReplace);
        gameList.set(replaceIndex, Tuple.of(gameId, description));
        fireTableRowsUpdated(replaceIndex, replaceIndex);
      }
    });
  }

  @Override
  public String getColumnName(final int column) {
    return Column.values()[column].toString();
  }

  int getColumnIndex(final Column column) {
    return column.ordinal();
  }

  @Override
  public int getColumnCount() {
    // -1 so we don't display the guid
    return Column.values().length - 1;
  }

  @Override
  public int getRowCount() {
    return gameList.size();
  }

  @Override
  public Object getValueAt(final int rowIndex, final int columnIndex) {
    final Column column = Column.values()[columnIndex];
    final GameDescription description = gameList.get(rowIndex).getSecond();
    switch (column) {
      case Host:
        return description.getHostName();
      case Round:
        return description.getRound();
      case Name:
        return description.getGameName();
      case Players:
        return description.getPlayerCount();
      case P:
        return (description.getPassworded() ? "*" : "");
      case B:
        return (description.getBotSupportEmail() != null && description.getBotSupportEmail().length() > 0 ? "-" : "");
      case GV:
        return description.getGameVersion();
      case EV:
        return description.getEngineVersion();
      case Status:
        return description.getStatus();
      case Comments:
        return description.getComment();
      case Started:
        return description.getStartDateTime();
      case GUID:
        return gameList.get(rowIndex).getFirst();
      default:
        throw new IllegalStateException("Unknown column:" + column);
    }
  }
}
