package dev.photosync.core.config;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

/**
 * Holds the live configuration and tells the rest of the mod when it changes.
 *
 * <p>The upload coordinator, the auto-capture timer and the browser all need to
 * react to a settings edit without the settings screen knowing they exist, so
 * they subscribe here instead.
 */
@Slf4j
public final class ConfigService {

    private final ConfigStore store;
    private final AtomicReference<PhotoSyncConfig> current;
    private final List<Consumer<PhotoSyncConfig>> listeners = new CopyOnWriteArrayList<>();

    public ConfigService(ConfigStore store) {
        this.store = store;
        this.current = new AtomicReference<>(store.load());
    }

    public PhotoSyncConfig current() {
        return current.get();
    }

    /**
     * Subscribes to later changes. Listeners run on whichever thread made the
     * change -- in practice the client thread, from the settings screen.
     */
    public void onChange(Consumer<PhotoSyncConfig> listener) {
        listeners.add(listener);
    }

    public void update(UnaryOperator<PhotoSyncConfig> change) {
        PhotoSyncConfig updated = current.updateAndGet(previous -> change.apply(previous).normalized(previous.provider()));
        persist(updated);
        for (Consumer<PhotoSyncConfig> listener : listeners) {
            try {
                listener.accept(updated);
            } catch (RuntimeException e) {
                log.error("A config listener failed; the change itself was still applied", e);
            }
        }
    }

    private void persist(PhotoSyncConfig config) {
        try {
            store.save(config);
        } catch (IOException e) {
            // Losing the write is bad but not fatal: the in-memory config is
            // already updated, so the session behaves as the player asked.
            log.error("Could not write {}", store.path(), e);
        }
    }

    /** Points the store at a new file, after the settings file has been moved there. */
    public void relocate(Path path) {
        store.relocate(path);
    }
}
