package mage.game.turn;

import mage.constants.PhaseStep;
import mage.constants.TurnPhase;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.players.Player;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author BetaSteward_at_googlemail.com
 */
public abstract class Phase implements Serializable {

    protected TurnPhase type;
    protected List<Step> steps = new ArrayList<>();
    protected EventType event;
    protected EventType preEvent;
    protected EventType postEvent;

    protected UUID activePlayerId;
    protected Step currentStep;
    protected int count;

    public abstract Phase copy();

    public Phase() {
    }

    protected Phase(final Phase phase) {
        this.type = phase.type;
        this.event = phase.event;
        this.preEvent = phase.preEvent;
        this.postEvent = phase.postEvent;
        this.activePlayerId = phase.activePlayerId;
        if (phase.currentStep != null) {
            this.currentStep = phase.currentStep.copy();
        }
        this.count = phase.count;
        for (Step step : phase.steps) {
            this.steps.add(step.copy());
        }
    }

    public TurnPhase getType() {
        return type;
    }

    public Step getStep() {
        return currentStep;
    }

    public void setStep(Step step) {
        this.currentStep = step;
    }

    public void resetCount() {
        count = 0;
    }

    public int getCount() {
        return count;
    }

    public boolean play(Game game, UUID activePlayerId) {
        if (game.isPaused() || game.checkIfGameIsOver()) {
            return false;
        }

        this.activePlayerId = activePlayerId;

        if (beginPhase(game, activePlayerId)) {

            for (Step step : steps) {
                if (game.isPaused() || game.checkIfGameIsOver()) {
                    return false;
                }
                if (game.getTurn().isEndTurnRequested() && step.getType() != PhaseStep.CLEANUP) {
                    continue;
                }
                currentStep = step;
                TurnMod skipStepMod = game.getState().getTurnMods().useNextSkipStep(activePlayerId, getStep().getType());
                if (skipStepMod == null) {
                    playStep(game);
                    if (game.executingRollback()) {
                        return true;
                    }
                } else {
                    Player player = game.getPlayer(skipStepMod.getPlayerId());
                    if (player != null) {
                        game.informPlayers(String.format("%s skips %s step%s",
                                player.getLogName(),
                                skipStepMod.getSkipStep().toString(),
                                skipStepMod.getInfo()
                        ));
                    }
                }
                if (!game.isSimulation() && checkStopOnStepOption(game)) {
                    return false;
                }

            }
            if (game.isPaused() || game.checkIfGameIsOver()) {
                return false;
            }
            count++;
            endPhase(game, activePlayerId);
            return true;
        }
        return false;
    }

    private boolean checkStopOnStepOption(Game game) {
        if (game.getOptions().stopOnTurn != null
                && game.getOptions().stopOnTurn <= game.getState().getTurnNum()
                && game.getOptions().stopAtStep == getStep().getType()) {
            game.pause();
            return true;
        }
        return false;
    }

    public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) {
        if (game.isPaused() || game.checkIfGameIsOver()) {
            return false;
        }

        this.activePlayerId = game.getActivePlayerId();
        Iterator<Step> it = steps.iterator();
        Step step;
        do {
            step = it.next();
            currentStep = step;
        } while (step.getType() != stepType);
        resumeStep(game, wasPaused);
        while (it.hasNext()) {
            step = it.next();
            if (game.isPaused() || game.checkIfGameIsOver()) {
                return false;
            }
            currentStep = step;
            TurnMod skipStepMod = game.getState().getTurnMods().useNextSkipStep(activePlayerId, currentStep.getType());
            if (skipStepMod == null) {
                playStep(game);
                if (game.executingRollback()) {
                    return true;
                }
            } else {
                Player player = game.getPlayer(skipStepMod.getPlayerId());
                if (player != null) {
                    game.informPlayers(String.format("%s skips %s step%s",
                            player.getLogName(),
                            skipStepMod.getSkipStep().toString(),
                            skipStepMod.getInfo()
                    ));
                }
            }
        }

        if (game.isPaused() || game.checkIfGameIsOver()) {
            return false;
        }
        count++;
        endPhase(game, activePlayerId);
        return true;
    }

    public boolean beginPhase(Game game, UUID activePlayerId) {
        if (!game.replaceEvent(new GameEvent(event, null, null, activePlayerId))) {
            game.fireEvent(new GameEvent(preEvent, null, null, activePlayerId));
            return true;
        }
        return false;
    }

    public void endPhase(Game game, UUID activePlayerId) {
        game.fireEvent(new GameEvent(postEvent, null, null, activePlayerId));
        game.getState().getTriggers().removeAbilitiesOfNonExistingSources(game); // e.g. tokens that left the battlefield
    }

    public void prePriority(Game game, UUID activePlayerId) {
        currentStep.beginStep(game, activePlayerId);
    }

    public void postPriority(Game game, UUID activePlayerId) {
        currentStep.endStep(game, activePlayerId);
        //20091005 - 500.4/703.4n
        game.emptyManaPools(null);
        //20091005 - 500.9
        playExtraSteps(game, currentStep.getType());
    }

    protected void playStep(Game game) {
        if (!currentStep.skipStep(game, activePlayerId)) {
            game.getState().increaseStepNum();
            prePriority(game, activePlayerId);
            if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
                currentStep.priority(game, activePlayerId, false);
                if (game.executingRollback()) {
                    return;
                }
            }
            if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
                postPriority(game, activePlayerId);
            }
        }
    }

    protected void resumeStep(Game game, boolean wasPaused) {
        boolean resuming = true;
        if (currentStep == null || currentStep.getStepPart() == null) {
            game.end();
            return;
        }
        switch (currentStep.getStepPart()) {
            case PRE:
                if (wasPaused) {
                    currentStep.resumeBeginStep(game, activePlayerId);
                    resuming = false;
                } else {
                    prePriority(game, activePlayerId);
                }
            case PRIORITY:
                if (!game.isPaused() && !game.checkIfGameIsOver()) {
                    currentStep.priority(game, activePlayerId, resuming);
                }
            case POST:
                if (!game.isPaused() && !game.checkIfGameIsOver()) {
                    postPriority(game, activePlayerId);
                }
        }
    }

    private void playExtraSteps(Game game, PhaseStep afterStep) {
        while (true) {
            TurnMod extraStepMod = game.getState().getTurnMods().useNextExtraStep(activePlayerId, afterStep);
            if (extraStepMod == null) {
                return;
            }
            currentStep = extraStepMod.getExtraStep();
            Player player = game.getPlayer(extraStepMod.getPlayerId());
            if (player != null && player.canRespond()) {
                game.informPlayers(String.format("%s takes an extra %s step%s",
                        player.getLogName(),
                        extraStepMod.getExtraStep().toString(),
                        extraStepMod.getInfo()
                ));
                playStep(game);
            } else {
                return;
            }
        }
    }

    /**
     * One card [[Okea, Splitter of Seconds]], adds extra beginning phases with
     * all but upkeep steps skipped.
     * To achieve that, this function is called right after creating the extra Phase,
     * before running it.
     */
    public void keepOnlyStep(PhaseStep step) {
        if (count != 0) {
            throw new IllegalStateException("Wrong code usage: illegal Phase modification once it started running");
        }
        this.steps = this.steps.stream().filter(s -> s.getType().equals(step)).collect(Collectors.toList());
        if (this.steps.isEmpty()) {
            throw new IllegalStateException("Wrong code usage: keepOnlyStep should not remove all the steps in a phase - " + step);
        }
    }

    public String toString() {
        return type.toString();
    }
}
