package ctbrec.recorder.postprocessing;

import ctbrec.Config;
import ctbrec.Recording;
import ctbrec.StringUtil;
import ctbrec.variableexpansion.ModelVariableExpander;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

import static java.util.Optional.ofNullable;

public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {

    public String fillInPlaceHolders(String input, PostProcessingContext ctx) {
        Recording rec = ctx.getRecording();
        Config config = ctx.getConfig();


        ModelVariableExpander modelExpander = new ModelVariableExpander(rec.getModel(), config, ctx.getRecorder());
        Map<String, Function<String, Optional<String>>> placeholderValueSuppliers = new HashMap<>(modelExpander.getPlaceholderValueSuppliers());
        placeholderValueSuppliers.put("recordingNotes", r -> getSanitizedRecordingNotes(rec));
        placeholderValueSuppliers.put("fileSuffix", r -> getFileSuffix(rec));
        placeholderValueSuppliers.put("recordingsDir", r -> Optional.of(config.getSettings().recordingsDir));
        placeholderValueSuppliers.put("absolutePath", r -> Optional.of(rec.getPostProcessedFile().getAbsolutePath()));
        placeholderValueSuppliers.put("absoluteParentPath", r -> Optional.of(rec.getPostProcessedFile().getParentFile().getAbsolutePath()));
        placeholderValueSuppliers.put("epochSecond", r -> ofNullable(rec.getStartDate()).map(Instant::getEpochSecond).map(l -> Long.toString(l))); // NOSONAR
        placeholderValueSuppliers.put("utcDateTime", pattern -> replaceUtcDateTime(rec, pattern));
        placeholderValueSuppliers.put("localDateTime", pattern -> replaceLocalDateTime(rec, pattern));

        String output = fillInPlaceHolders(input, placeholderValueSuppliers);
        return output;
    }

    private String fillInPlaceHolders(String input, Map<String, Function<String, Optional<String>>> placeholderValueSuppliers) {
        boolean somethingReplaced;
        do {
            somethingReplaced = false;
            int end = input.indexOf("}");
            if (end > 0) {
                int start = input.substring(0, end).lastIndexOf("${");
                if (start >= 0) {
                    String placeholder = input.substring(start, end + 1);
                    String placeholderName = placeholder.substring(2, placeholder.length() - 1);
                    String defaultValue;
                    String expression = null;
                    int questionMark = placeholder.indexOf('?');
                    if (questionMark > 0) {
                        placeholderName = placeholder.substring(2, questionMark);
                        defaultValue = placeholder.substring(questionMark + 1, placeholder.length() - 1);
                    } else {
                        defaultValue = "";
                    }
                    int bracket = placeholder.indexOf('(');
                    if (bracket > 0) {
                        placeholderName = placeholder.substring(2, bracket);
                        expression = placeholder.substring(bracket + 1, placeholder.indexOf(')', bracket));
                    }

                    final String name = placeholderName;
                    Optional<String> optionalValue = placeholderValueSuppliers.getOrDefault(name, r -> Optional.of(name)).apply(expression);
                    String value = optionalValue.orElse(defaultValue);
                    StringBuilder sb = new StringBuilder(input);
                    String output = sb.replace(start, end + 1, value).toString();
                    somethingReplaced = !Objects.equals(input, output);
                    input = output;
                }
            }
        } while (somethingReplaced);
        return input;
    }

    private Optional<String> replaceUtcDateTime(Recording rec, String pattern) {
        return replaceDateTime(rec, pattern, ZoneOffset.UTC);
    }

    private Optional<String> replaceLocalDateTime(Recording rec, String filename) {
        return replaceDateTime(rec, filename, ZoneId.systemDefault());
    }

    private Optional<String> replaceDateTime(Recording rec, String pattern, ZoneId zone) {
        pattern = pattern != null ? pattern : "yyyy-MM-dd_HH-mm-ss";
        return getDateTime(rec, pattern, zone);
    }

    private Optional<String> getDateTime(Recording rec, String pattern, ZoneId zone) {
        return Optional.ofNullable(rec.getStartDate()) //
                .map(DateTimeFormatter.ofPattern(pattern) //
                        .withLocale(Locale.getDefault()) //
                        .withZone(zone) //
                        ::format);
    }

    private Optional<String> getFileSuffix(Recording rec) {
        if (rec.isSingleFile()) {
            String filename = rec.getPostProcessedFile().getName();
            return Optional.of(filename.substring(filename.lastIndexOf('.') + 1));
        } else {
            return Optional.empty();
        }
    }

    private Optional<String> getSanitizedRecordingNotes(Recording rec) {
        Optional<String> notes = ofNullable(rec.getNote());
        return notes.map(StringUtil::sanitize);
    }
}
