package pp.battleship.game.singlemode;

import pp.battleship.game.client.BattleshipClient;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerInterpreter;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.IntPoint;
import pp.battleship.model.dto.ShipMapDTO;
import pp.util.RandomPositionIterator;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import static pp.battleship.Resources.lookup;

/**
 * RobotClient simulates a client in the Battleship game.
 * It handles its own shooting targets and acts as a sender of client messages.
 * The RobotClient can shoot at predefined targets or generate random targets if predefined ones are exhausted.
 */
class RobotClient implements ServerInterpreter {
    private static final Logger LOGGER = System.getLogger(RobotClient.class.getName());
    private final BattleshipClient app;
    private final ServerConnectionMockup connection;
    private final BattleshipClientConfig config;
    private final ShipMapDTO dto;
    private final Iterator<IntPoint> targetIterator;
    private final Iterator<IntPoint> randomPositionIterator;
    private final Set<IntPoint> shotTargets = new HashSet<>();
    private final Timer timer = new Timer(true);

    /**
     * Constructs a RobotClient instance with the given connection and configuration.
     * Initializes the shooting targets from the configuration.
     *
     * @param app        The BattleshipApp instance, used to enqueue actions on the main thread
     * @param connection The ServerConnectionMockup instance
     * @param config     The BattleshipClientConfig instance
     * @param dto        The ShipMap dto specified at app start
     */
    RobotClient(BattleshipClient app, ServerConnectionMockup connection, BattleshipClientConfig config, ShipMapDTO dto) {
        this.app = app;
        this.connection = connection;
        this.config = config;
        this.dto = dto;
        this.targetIterator = config.getRobotTargets();
        this.randomPositionIterator = new RandomPositionIterator<>(IntPoint::new, config.getMapWidth(), config.getMapHeight());
    }

    /**
     * Schedules the RobotClient to take a shot after the specified delay.
     */
    private void shoot() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                app.enqueue(RobotClient.this::robotShot);
            }
        }, config.getDelay());
    }

    /**
     * Makes the RobotClient take a shot by sending a ShootMessage with the target position.
     */
    private void robotShot() {
        connection.sendRobotMessage(new ShootMessage(getShotPosition()));
    }

    /**
     * Determines the next shot position. If predefined targets are available, uses the next target.
     * Otherwise, generates a random target that has not been shot at before.
     *
     * @return the next shot position as IntPosition
     */
    private IntPoint getShotPosition() {
        while (true) {
            final IntPoint target = targetIterator.hasNext() ? targetIterator.next() : randomPositionIterator.next();
            if (shotTargets.add(target)) return target;
        }
    }

    /**
     * Receives GameDetails, creates and sends the ShipMap to the mock server.
     *
     * @param details The game details
     */
    @Override
    public void received(GameDetails details) {
        if (!dto.fits(details))
            throw new RuntimeException(lookup("map.doesnt.fit"));
        app.enqueue(() -> connection.sendRobotMessage(new MapMessage(dto.getShips())));
    }

    /**
     * Receives the StartBattleMessage and updates the turn status.
     * If it is RobotClient's turn to shoot, schedules a shot using shoot();
     *
     * @param msg The start battle message
     */
    @Override
    public void received(StartBattleMessage msg) {
        LOGGER.log(Level.INFO, "Received StartBattleMessage: {0}", msg); //NON-NLS
        if (msg.isMyTurn())
            shoot();
    }

    /**
     * Receives an effect message, logs it, and updates the turn status.
     * If it is RobotClient's turn to shoot, schedules a shot using shoot();
     *
     * @param msg The effect message
     */
    @Override
    public void received(EffectMessage msg) {
        LOGGER.log(Level.INFO, "Received EffectMessage: {0}", msg); //NON-NLS
        if (msg.isMyTurn())
            shoot();
    }
}