package tools.messages;

import ch.jalu.configme.configurationdata.ConfigurationData;
import ch.jalu.configme.properties.Property;
import ch.jalu.configme.resource.PropertyReader;
import ch.jalu.configme.resource.YamlFileResource;
import fr.xephi.authme.message.updater.MessageKeyConfigurationData;
import fr.xephi.authme.message.updater.MessageUpdater;
import fr.xephi.authme.message.updater.MigraterYamlFileResource;
import org.bukkit.configuration.file.FileConfiguration;
import tools.utils.FileIoUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Writes to a messages file, adding comments with the default file's message where
 * entries are missing.
 * <p>
 * This writer writes to the file twice: once with ConfigMe to ensure a proper order
 * of the properties and comments, and a second time to add any custom comments that
 * were at the top of the file and to separate comments by new lines (which ConfigMe
 * currently doesn't support).
 */
public final class MessagesFileWriter {

    /** Marker used inside a text to signal that it should be a comment later on. */
    private static final String COMMENT_MARKER = "::COMMENT::";

    private static final Pattern SPACES_BEFORE_TEXT_PATTERN = Pattern.compile("(\\s+)\\w.*");

    /** The messages file to update. */
    private final File file;
    /** Messages from the default file. */
    private final FileConfiguration defaultFile;

    private MessagesFileWriter(File file, FileConfiguration defaultFile) {
        this.file = file;
        this.defaultFile = defaultFile;
    }

    public static void writeToFileWithCommentsFromDefault(File file, FileConfiguration configuration) {
        new MessagesFileWriter(file, configuration).performWrite();
    }

    private void performWrite() {
        // Initialize ConfigMe classes
        MessageKeyConfigurationData configurationData = MessageUpdater.createConfigurationData();
        YamlFileResource resource = new MigraterYamlFileResource(file);
        PropertyReader reader = resource.createReader();
        configurationData.initializeValues(reader);

        // Store initial comments so we can add them back later
        List<String> initialComments = getInitialUserComments(configurationData);

        // Create property resource with new defaults, save with ConfigMe for proper sections & comments
        addMissingMessagesWithCommentMarker(reader, configurationData);
        resource.exportProperties(configurationData);

        // Go through the newly saved file and replace texts with comment marker to actual YAML comments
        // and add initial comments back to the file
        rewriteToFileWithComments(initialComments);
    }

    /**
     * Returns the comments at the top which are custom to the file.
     *
     * @param configurationData the configuration data
     * @return any custom comments at the top of the file, for later usage
     */
    private List<String> getInitialUserComments(ConfigurationData configurationData) {
        final List<String> initialComments = new ArrayList<>();
        final String firstCommentByConfigMe = getFirstCommentByConfigMe(configurationData);

        for (String line : FileIoUtils.readLinesFromFile(file.toPath())) {
            if (line.isEmpty() || line.startsWith("#") && !line.equals(firstCommentByConfigMe)) {
                initialComments.add(line);
            } else {
                break;
            }
        }
        // Small fix: so we can keep running this writer and get the same result, we need to make sure that any ending
        // empty lines are removed
        for (int i = initialComments.size() - 1; i >= 0; --i) {
            if (initialComments.get(i).isEmpty()) {
                initialComments.remove(i);
            } else {
                break;
            }
        }
        return initialComments;
    }

    /**
     * Returns the first comment generated by ConfigMe (comment of the first root path).
     *
     * @param configurationData the configuration data
     * @return first comment which is generated by ConfigMe
     */
    private static String getFirstCommentByConfigMe(ConfigurationData configurationData) {
        String firstRootPath = configurationData.getProperties().get(0).getPath().split("\\.")[0];
        return "# " + configurationData.getCommentsForSection(firstRootPath).get(0);
    }

    /**
     * Adds a text with a {@link #COMMENT_MARKER} for all properties which are not yet present in the reader.
     *
     * @param reader the property reader
     * @param configurationData the configuration data
     */
    private void addMissingMessagesWithCommentMarker(PropertyReader reader,
                                                     MessageKeyConfigurationData configurationData) {
        for (Property<String> property : configurationData.getAllMessageProperties()) {
            String text = reader.getString(property.getPath());
            if (text == null) {
                configurationData.setValue(property, COMMENT_MARKER + defaultFile.getString(property.getPath()));
            }
        }
    }

    /**
     * Writes to the file again, adding the provided initial comments at the top of the file and converting
     * any entries marked with {@link #COMMENT_MARKER} to YAML comments.
     *
     * @param initialComments the comments at the top of the file to add back
     */
    private void rewriteToFileWithComments(List<String> initialComments) {
        List<String> newLines = new ArrayList<>(initialComments);

        for (String line : FileIoUtils.readLinesFromFile(file.toPath())) {
            if (line.contains(COMMENT_MARKER)) {
                String lineAsYamlComment = convertLineWithCommentMarkerToYamlComment(line);
                newLines.add(lineAsYamlComment);
            } else if (line.startsWith("#") && !newLines.isEmpty()) {
                // ConfigMe doesn't support empty line between comments, so here we check if we have a comment that
                // isn't at the very top and sneak in an empty line if so.
                newLines.add("");
                newLines.add(line);
            } else if (!line.isEmpty()) {
                // ConfigMe adds an empty line at the beginning, so check here that we don't include any empty lines...
                newLines.add(line);
            }
        }
        newLines.add(""); // Makes sure file ends with new line

        FileIoUtils.writeToFile(file.toPath(), String.join("\n", newLines));
    }

    private static String convertLineWithCommentMarkerToYamlComment(String line) {
        Matcher matcher = SPACES_BEFORE_TEXT_PATTERN.matcher(line);
        if (matcher.matches()) {
            String spacesBefore = matcher.group(1);
            return spacesBefore + "# TODO " + line.replace(COMMENT_MARKER, "").trim();
        } else {
            throw new IllegalStateException("Space-counting pattern unexpectedly did not match on line '" + line + "'");
        }
    }
}
