/*
 * Copyright 2019 MovingBlocks
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.terasology.config.flexible;

import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.context.Context;
import org.terasology.engine.SimpleUri;
import org.terasology.engine.TerasologyConstants;
import org.terasology.engine.module.ModuleManager;
import org.terasology.engine.paths.PathManager;
import org.terasology.module.ModuleEnvironment;
import org.terasology.persistence.typeHandling.TypeHandlerLibrary;
import org.terasology.registry.InjectionHelper;
import org.terasology.utilities.ReflectionUtil;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;

public class AutoConfigManager {
    private static final Logger LOGGER = LoggerFactory.getLogger(AutoConfigManager.class);
    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();

    private final Set<AutoConfig> loadedConfigs = Sets.newHashSet();
    private final TypeHandlerLibrary typeHandlerLibrary;

    private Context context;

    public AutoConfigManager(TypeHandlerLibrary typeHandlerLibrary) {
        this.typeHandlerLibrary = typeHandlerLibrary;
    }

    public void loadConfigsIn(Context context) {
        ModuleEnvironment environment = context.get(ModuleManager.class).getEnvironment();

        this.context = context;

        for (Class<? extends AutoConfig> configClass : environment.getSubtypesOf(AutoConfig.class)) {
            if (context.get(configClass) != null) {
                // We've already initialized this config before
                continue;
            }

            SimpleUri configId = ReflectionUtil.getSimpleUriFor(configClass, environment);
            loadConfig(configClass, configId);
        }

        this.context = null;
    }

    private <T extends AutoConfig> void loadConfig(Class<T> clazz, SimpleUri id) {
        Optional<T> optionalConfig = InjectionHelper.safeCreateWithConstructorInjection(clazz, context);

        if (!optionalConfig.isPresent()) {
            LOGGER.error("Unable to instantiate config {}", id);
            return;
        }

        T config = optionalConfig.get();
        config.setId(id);

        loadedConfigs.add(config);
        context.put(clazz, config);

        loadSettingsFromDisk(clazz, config);
    }

    private <T extends AutoConfig> void loadSettingsFromDisk(Class<T> configClass, T config) {
        AutoConfigSerializer<T> serializer = new AutoConfigSerializer<>(configClass, typeHandlerLibrary);

        Path configPath = getConfigPath(config.getId());

        if (!Files.exists(configPath)) {
            return;
        }

        try (Reader reader = Files.newBufferedReader(configPath, TerasologyConstants.CHARSET)) {
            serializer.deserializeOnto(config, GSON.fromJson(reader, JsonElement.class));
        } catch (IOException e) {
            LOGGER.error("Error while loading config {} from disk", config.getId(), e);
        }
    }

    public void saveConfigsToDisk() {
        // TODO: Come up with uniform mechanism to save configs;
        //  currently hardcoded Config is saved right after it is modified
        for (AutoConfig loadedConfig : loadedConfigs) {
            saveConfigToDisk(loadedConfig);
        }
    }

    private <T extends AutoConfig> void saveConfigToDisk(T config) {
        // TODO: Save when screen for config closed
        Class<T> configClass = (Class<T>) config.getClass();
        AutoConfigSerializer<T> serializer = new AutoConfigSerializer<>(configClass, typeHandlerLibrary);

        Path configPath = getConfigPath(config.getId());

        try (BufferedWriter writer = Files.newBufferedWriter(configPath, TerasologyConstants.CHARSET)) {
            JsonElement json = serializer.serialize(config);
            GSON.toJson(json, writer);
        } catch (IOException e) {
            LOGGER.error("Error while saving config {} to disk", config.getId(), e);
        }
    }

    private Path getConfigPath(SimpleUri configId) {
        Path filePath = PathManager.getInstance()
                            .getConfigsPath()
                            .resolve(configId.getModuleName().toString())
                            .resolve(configId.getObjectName().toString() + ".cfg");

        // This call ensures that the entire directory structure (like configs/engine/) exists.
        ensureDirectoryExists(filePath);
        return filePath;
    }

    private void ensureDirectoryExists(Path filePath) {
        try {
            Files.createDirectories(filePath.getParent());
        } catch (Exception e) {
            throw new RuntimeException("Cannot create directory for flexibleConfig " + filePath.getFileName() + "!");
        }
    }
}

@@ -1520,22 +1520,22 @@
  Logger 
-LOGGER
+logger
  = Logge
@@ -1609,20 +1609,20 @@
 al Gson 
-GSON
+gson
  = new G
@@ -2807,38 +2807,38 @@
 ) %7B%0A            
-LOGGER
+logger
 .error(%22Unable t
@@ -3567,20 +3567,20 @@
 config, 
-GSON
+gson
 .fromJso
@@ -3648,38 +3648,38 @@
 ) %7B%0A            
-LOGGER
+logger
 .error(%22Error wh
@@ -4573,12 +4573,12 @@
     
-GSON
+gson
 .toJ
@@ -4646,14 +4646,14 @@
     
-LOGGER
+logger
 .err

Rename GSON and LOGGER to gson and logger in AutoConfigManager