package com.mineplex.studio.cache;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Optional;
import net.minecraft.Util;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.Block;
import org.slf4j.Logger;

public class StartupCache {
    private static final StartupCache INSTANCE = new StartupCache();
    private static final Logger log = org.slf4j.LoggerFactory.getLogger(StartupCache.class);

    public static StartupCache getInstance() {
        return INSTANCE;
    }

    private final Path cachePath = Path.of(".cache");
    private final Gson gson = new GsonBuilder().create();

    private final boolean hasNewBlockBehaviourCache;
    private final BlockBehaviourCacheMap blockBehaviourCache;

    private StartupCache() {
        try {
            Files.createDirectories(cachePath);
        } catch (Exception exception) {
            throw new RuntimeException(exception);
        }

        this.blockBehaviourCache = loadBlockBehaviourCache();
        this.hasNewBlockBehaviourCache =
                blockBehaviourCache.getBlockBehaviourCache().isEmpty();
    }

    private void writeToFile(Path file, Object content) {
        // Ensure the parent directory exists
        try {
            Files.createDirectories(file.getParent());
        } catch (IOException e) {
            log.error("Failed to create parent directories", e);
            return;
        }

        try (BufferedWriter fileWriter = Files.newBufferedWriter(file, StandardOpenOption.CREATE)) {
            gson.toJson(content, fileWriter);
        } catch (Exception exception) {
            log.error("Failed to write to file", exception);
        }
    }

    private <T> Optional<T> readFromFile(Path file, Class<T> type) {
        if (!Files.exists(file)) {
            return Optional.empty();
        }

        try (BufferedReader fileReader = Files.newBufferedReader(file)) {
            return Optional.ofNullable(gson.fromJson(fileReader, type));
        } catch (Exception exception) {
            log.error("Failed to read from file {}.", file, exception);
        }

        return Optional.empty();
    }

    private Path getFile(final String fileName) {
        return cachePath.resolve(fileName);
    }

    private Path getSearchRadiusIterationListFile() {
        return getFile("player_chunk_search_radius.json");
    }

    public Optional<long[][]> getCachedSearchRadiusIterationList() {
        final Path file = getSearchRadiusIterationListFile();
        return readFromFile(file, long[][].class);
    }

    public void cacheSearchRadiusIterationList(final long[][] searchRadiusIterationList) {
        final Path file = getSearchRadiusIterationListFile();
        writeToFile(file, searchRadiusIterationList);
    }

    private Path getBlockBehaviourCacheFile() {
        return getFile("block_behaviour.json");
    }

    private BlockBehaviourCacheMap loadBlockBehaviourCache() {
        final Path file = getBlockBehaviourCacheFile();
        return readFromFile(file, BlockBehaviourCacheMap.class)
                .orElseGet(() -> new BlockBehaviourCacheMap(new HashMap<>()));
    }

    private String getBlockId(Block block) {
        return Util.makeDescriptionId("block", BuiltInRegistries.BLOCK.getKey(block));
    }

    public Optional<BlockBehaviourCache> getCachedBlockBehaviourCache(Block block) {
        return Optional.ofNullable(blockBehaviourCache.getBlockBehaviourCache().get(getBlockId(block)));
    }

    public void cacheBlockBehaviourCache(Block block, BlockBehaviourCache cache) {
        blockBehaviourCache.getBlockBehaviourCache().put(getBlockId(block), cache);
    }

    public void writeBlockBehaviourCache() {
        if (!hasNewBlockBehaviourCache) {
            return;
        }

        final Path file = getBlockBehaviourCacheFile();
        writeToFile(file, blockBehaviourCache);
    }

    public void clearBlockBehaviourCache() {
        blockBehaviourCache.getBlockBehaviourCache().clear();
    }
}
