package games.strategy.triplea;

import java.awt.Frame;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.SwingUtilities;

import games.strategy.debug.ClientLogger;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.IUnitFactory;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.framework.IGame;
import games.strategy.engine.framework.IGameLoader;
import games.strategy.engine.framework.LocalPlayers;
import games.strategy.engine.framework.ServerGame;
import games.strategy.engine.gamePlayer.IGamePlayer;
import games.strategy.engine.message.IChannelSubscribor;
import games.strategy.engine.message.IRemote;
import games.strategy.sound.ClipPlayer;
import games.strategy.sound.DefaultSoundChannel;
import games.strategy.sound.HeadlessSoundChannel;
import games.strategy.sound.ISound;
import games.strategy.sound.SoundPath;
import games.strategy.triplea.ai.fastAI.FastAI;
import games.strategy.triplea.ai.proAI.ProAI;
import games.strategy.triplea.ai.weakAI.DoesNothingAI;
import games.strategy.triplea.ai.weakAI.WeakAI;
import games.strategy.triplea.delegate.EditDelegate;
import games.strategy.triplea.player.ITripleAPlayer;
import games.strategy.triplea.ui.HeadlessUIContext;
import games.strategy.triplea.ui.IUIContext;
import games.strategy.triplea.ui.TripleAFrame;
import games.strategy.triplea.ui.display.HeadlessDisplay;
import games.strategy.triplea.ui.display.ITripleADisplay;
import games.strategy.triplea.ui.display.TripleADisplay;

@MapSupport
public class TripleA implements IGameLoader {
  private static final long serialVersionUID = -8374315848374732436L;
  public static final String HUMAN_PLAYER_TYPE = "Human";
  public static final String WEAK_COMPUTER_PLAYER_TYPE = "Easy (AI)";
  public static final String FAST_COMPUTER_PLAYER_TYPE = "Fast (AI)";
  public static final String PRO_COMPUTER_PLAYER_TYPE = "Hard (AI)";
  public static final String DOESNOTHINGAI_COMPUTER_PLAYER_TYPE = "Does Nothing (AI)";
  protected transient ITripleADisplay display;

  protected transient ISound soundChannel;
  protected transient IGame game;

  @Override
  public Set<IGamePlayer> createPlayers(final Map<String, String> playerNames) {
    final Set<IGamePlayer> players = new HashSet<>();
    for (final String name : playerNames.keySet()) {
      final String type = playerNames.get(name);
      if (type.equals(WEAK_COMPUTER_PLAYER_TYPE)) {
        players.add(new WeakAI(name, type));
      } else if (type.equals(FAST_COMPUTER_PLAYER_TYPE)) {
        players.add(new FastAI(name, type));
      } else if (type.equals(PRO_COMPUTER_PLAYER_TYPE)) {
        players.add(new ProAI(name, type));
      } else if (type.equals(DOESNOTHINGAI_COMPUTER_PLAYER_TYPE)) {
        players.add(new DoesNothingAI(name, type));
      } else if (type.equals(HUMAN_PLAYER_TYPE) || type.equals(CLIENT_PLAYER_TYPE)) {
        final TripleAPlayer player = new TripleAPlayer(name, type);
        players.add(player);
      } else {
        throw new IllegalStateException("Player type not recognized:" + type);
      }
    }
    return players;
  }

  @Override
  public void shutDown() {
    if (game != null && soundChannel != null) {
      game.removeSoundChannel(soundChannel);
      // set sound channel to null to handle the case of shutdown being called multiple times.
      // If/when shutdown is called exactly once, then the null assignment should be unnecessary.
      soundChannel = null;
    }

    if (display != null) {
      game.removeDisplay(display);
      display.shutDown();
      display = null;
    }
  }


  @Override
  public void startGame(final IGame game, final Set<IGamePlayer> players, final boolean headless)
      throws InvocationTargetException {
    this.game = game;
    if (game.getData().getDelegateList().getDelegate("edit") == null) {
      // An evil hack: instead of modifying the XML, force an EditDelegate by adding one here
      final EditDelegate delegate = new EditDelegate();
      delegate.initialize("edit", "edit");
      game.getData().getDelegateList().addDelegate(delegate);
      if (game instanceof ServerGame) {
        ((ServerGame) game).addDelegateMessenger(delegate);
      }
    }
    final LocalPlayers localPlayers = new LocalPlayers(players);
    if (headless) {
      final IUIContext uiContext = new HeadlessUIContext();
      uiContext.setDefaultMapDir(game.getData());
      uiContext.setLocalPlayers(localPlayers);
      display = new HeadlessDisplay();
      soundChannel = new HeadlessSoundChannel();
      game.addDisplay(display);
      game.addSoundChannel(soundChannel);

      // technically not needed because we won't have any "local human players" in a headless game.
      connectPlayers(players, null);
    } else {
      try {
        SwingUtilities.invokeAndWait(() -> {
          final TripleAFrame frame;
          frame = new TripleAFrame(game, localPlayers);
          display = new TripleADisplay(frame);
          game.addDisplay(display);
          soundChannel = new DefaultSoundChannel(localPlayers);
          game.addSoundChannel(soundChannel);
          frame.setSize(700, 400);
          frame.setVisible(true);
          ClipPlayer.play(SoundPath.CLIP_GAME_START);
          connectPlayers(players, frame);
          frame.setExtendedState(Frame.MAXIMIZED_BOTH);
          frame.toFront();
        });
      } catch (final InterruptedException e) {
        ClientLogger.logQuietly(e);
      }
    }

  }

  private static void connectPlayers(final Set<IGamePlayer> players, final TripleAFrame frame) {
    for (final IGamePlayer player : players) {
      if (player instanceof TripleAPlayer) {
        ((TripleAPlayer) player).setFrame(frame);
      }
    }
  }

  @Override
  public String[] getServerPlayerTypes() {
    return new String[] {HUMAN_PLAYER_TYPE, WEAK_COMPUTER_PLAYER_TYPE, FAST_COMPUTER_PLAYER_TYPE,
        PRO_COMPUTER_PLAYER_TYPE, DOESNOTHINGAI_COMPUTER_PLAYER_TYPE};
  }

  @Override
  public Class<? extends IChannelSubscribor> getDisplayType() {
    return ITripleADisplay.class;
  }

  @Override
  public Class<? extends IChannelSubscribor> getSoundType() {
    return ISound.class;
  }

  @Override
  public Class<? extends IRemote> getRemotePlayerType() {
    return ITripleAPlayer.class;
  }

  @Override
  public IUnitFactory getUnitFactory() {
    return new IUnitFactory() {
      private static final long serialVersionUID = 5684926837825766505L;

      @Override
      public Unit createUnit(final UnitType type, final PlayerID owner, final GameData data) {
        return new TripleAUnit(type, owner, data);
      }
    };
  }

  @Override
  public String toString() {
    return "TripleA[]";
  }
}
