package be.iminds.ilabt.jfed.experimenter_gui.editor.rspec_validation;

import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecInterface;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecNode;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.stage.Window;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 1/22/2015
 * Time: 16:51
 */
public class UniqueCliendIdValidator implements RspecValidator {
    @Override
    public RspecValidationResult validateRSpec(RspecCheckReason reason, RequestRspecSource requestRspecSource, Window dialogOwner) {
        FXModelRspec modelRspec = requestRspecSource.getModelRspec(ModelRspecType.FX);
        assert (modelRspec != null);

        Set<String> clientIds = new HashSet<>();
        Set<FXRspecNode> nodesWithDuplicateIds = new HashSet<>();

        //find all duplicate nodes
        for (FXRspecNode node : modelRspec.getNodes()) {
            if (clientIds.contains(node.getClientId())) {
                nodesWithDuplicateIds.add(node);
            }
            clientIds.add(node.getClientId());
        }

        if (!nodesWithDuplicateIds.isEmpty()) {
            //we found a duplicate node Id!

            String abortText = "Abort";
            String fixText = "Fix and continue";
            String ignoreText = "Ignore";
            boolean showIgnore = false;
            switch (reason) {
                case SWITCH_TO_VISUAL_EDITOR: {
                    abortText = "Abort and return to editor";
                    ignoreText = "Ignore";
                    showIgnore = true;
                    break;
                }
                case RUN_EXPERIMENT: {
                    abortText = "Abort and continue editing";
                    ignoreText = "Ignore and Run Experiment Anyway";
                    fixText = "Fix and Run Experiment";
                    showIgnore = true;
                    break;
                }
                case CHECK_RSPEC: {
                    abortText = "Ok";
                    fixText = "Fix";
                    showIgnore = false;
                    break;
                }
                case OPENED_FILE: {
                    abortText = "Ok";
                    fixText = "Fix";
                    showIgnore = false;
                    break;
                }
            }


            List<String> duplicateIds =
                    nodesWithDuplicateIds.stream().map(FXRspecNode::getClientId).distinct().sorted().collect(Collectors.toList());

            ButtonType fixButtonType = new ButtonType(fixText, ButtonBar.ButtonData.YES);
            ButtonType ignoreButtonType = new ButtonType(ignoreText, ButtonBar.ButtonData.CANCEL_CLOSE);
            ButtonType abortButtonType = new ButtonType(abortText, ButtonBar.ButtonData.CANCEL_CLOSE);

            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.initOwner(dialogOwner);
            alert.setContentText(String.format("Problem in RSpec: the %s %s %s been used multiple times." +
                            " This is confusing and will likely lead to problems during deployment.",
                    duplicateIds.size() > 1 ? "id's" : "id",
                    duplicateIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(", ")),
                    duplicateIds.size() > 1 ? "have" : "has"));
            alert.setTitle("Duplicate id detected!");
            if (showIgnore)
                alert.getButtonTypes().setAll(fixButtonType, ignoreButtonType, abortButtonType);
            else
                alert.getButtonTypes().setAll(fixButtonType, abortButtonType);
            ButtonType chosenAction = alert.showAndWait().orElse(null);

            if (chosenAction == fixButtonType) {
                for (FXRspecNode node : nodesWithDuplicateIds) {
                    String oldClientId = node.getClientId();
                    assert oldClientId != null;
                    String nextId = createNextId(node.getClientId());
                    while (clientIds.contains(nextId)) {
                        nextId = createNextId(nextId);
                    }
                    node.setClientId(nextId);
                    for (FXRspecInterface fxRspecInterface : node.getInterfaces()) {
                        fxRspecInterface.setClientId(fxRspecInterface.getClientId().replace(oldClientId, nextId));
                    }

                    clientIds.add(nextId);

                }

                return new RspecValidationResult(modelRspec); //the original modelRspec is also the fixed one
            }

            switch (reason) {
                case SWITCH_TO_VISUAL_EDITOR: //fall-through
                case RUN_EXPERIMENT:
                    return new RspecValidationResult(requestRspecSource, chosenAction == abortButtonType/*abort*/);
                case CHECK_RSPEC: //fall-through
                case OPENED_FILE:
                    return new RspecValidationResult(requestRspecSource, false/* == ignore*/);
                default:
                    throw new IllegalStateException("Unexpected reason");
            }
        } else {
            return null; //everything is OK.
        }
    }

    static String createNextId(String id) {
        int numberOfTrailingDigits = 0;

        while (id.length() > numberOfTrailingDigits && Character.isDigit(id.charAt(id.length() - numberOfTrailingDigits - 1))) {
            numberOfTrailingDigits++;
        }

        int trailingNumber = 1; //default value for when the id has no trailing digits

        if (numberOfTrailingDigits > 0) {
            //fetch the current number
            trailingNumber = Integer.parseInt(id.substring(id.length() - numberOfTrailingDigits));

            trailingNumber++;
        }

        return id.substring(0, id.length() - numberOfTrailingDigits) + Integer.toString(trailingNumber);
    }
}
