/*
 *
 * ConfigManager
 *
 * This class is the central configuration file manager for all other classes and acts as a utility class for other classes to use.
 * It is typical to define this class as a general object (not static instance) for each class in single-threaded actions. If you need
 * a special function or action from this class; it is recommended to add it to this class and not make your own.
 *
 *
*/

package jesse.keeblarcraft.ConfigMgr;

import java.io.FileWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Paths;

import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;

import java.util.List;

import org.apache.commons.io.FileUtils;

import java.util.ArrayList;
import java.util.HashMap;

import jesse.keeblarcraft.Keeblarcraft;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtList;

public class ConfigManager {
    // Pedantic empty constructor
    private static final String GLOBAL_CONFIG = "config/keeblarcraft/";

    public ConfigManager() {}

    // Get a File reference to a file that is created on disk
    private File GetFile(String confFile) {
        File file = null;
        System.out.println("Get file called for " + GLOBAL_CONFIG + confFile);
        try {
            System.out.println("GetFile Cur Dir: " + Paths.get("").toAbsolutePath().toString());
            file = new File(GLOBAL_CONFIG + confFile);
        } catch (Exception e) {}
        return file;
    }

    // Returns the parent path to a given input.
    // Ex: /home/Downloads/file.txt will return /home/Downloads/
    // Ex: /home/Downloads will return /home/
    // Ex: / will return /
    private String GetPathOfFile(String file) {
        String pathToFile = "";
        if (file != null) {
            for (int i = file.length() - 1; i >= 0; i--) {
                if (file.charAt(i) != File.separatorChar) {
                    continue;
                }
                else {
                    // Trim and break
                    pathToFile = file.substring(0, i);
                    break;
                }
            }
        }

        System.out.println("PathOfFile: " + pathToFile);
        return pathToFile;
    }

    // Creates a directory and all necessary parent directories listed in dirname (under global config area)
    public Boolean CreateDirectory(String dirName) {
        Boolean success = false;
        File directory = GetFile(dirName);

        try {
            if (directory != null) {
                if (!directory.exists())
                {
                    success = directory.mkdirs();
                } else if (directory.isDirectory()) {
                    success = true;
                } else {
                    System.out.println("Directory " + dirName + " is an already existing file!");
                }
            }
        } catch (Exception e) {
            System.out.println("Failed to create directory with name " + dirName);
            e.printStackTrace();
        }
        return success;
    }

    // Create a file on the disk
    public Boolean CreateFile(String fileName) {
        Boolean success = false;
        File file = GetFile(fileName);
        String parentDir = GetPathOfFile(fileName);

        // CreateDirectory will verify that the parent directories exist & at least the parent directory is in fact a directory
        if (file != null && !file.exists() && CreateDirectory(parentDir)) {
            try {
                success = file.createNewFile();
            } catch (Exception e) {
                System.out.println("Failed to create file " + fileName);
                e.printStackTrace();
            }
        } else {
            System.out.println("CreateFile failed to finish. File may have been null (empty string input, unlikely), or parent directories could not be created/have overlapped name");
            success = false;
        }
        return success;
    }

    public Boolean DeleteFile(String fileName) {
        Boolean success = false;
        File file = GetFile(fileName);

        success = !file.exists();

        if (!success) {
            try {
                success = file.delete();
            } catch (Exception e) {
                System.out.println("Failed to delete file " + fileName);
                e.printStackTrace();
            }
        }
        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         WriteNbtListToFile
    ///
    /// @param[in]  fileName is the file location to write to
    ///
    /// @param[in]  key is the compound key type to store
    ///
    /// @param[in]  data is the NbtList to write
    ///
    /// @brief      Writes NbtList data to disk
    /////////////////////////////////////////////////////////////////////////////
    public void WriteNbtListToFile(String fileName, String key, NbtList data) {
        // fileName = "config/keeblarcraft/" + fileName;

        File file = GetFile(fileName);
        if (!file.exists()) {
            file.getParentFile().mkdirs();
        }

        try {
            NbtCompound compound = new NbtCompound();
            compound.put(key, data);
            NbtIo.writeCompressed(compound, file);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ReadAllNbtListFromDirectory
    ///
    /// @param[in]  dir is the directory path to read from
    ///
    /// @param[in]  listType is the NBT Compound list type
    ///
    /// @brief      Reads in NBTList data from a directory; using file names as key.
    ///             If a file is read in correctly, the file is deleted
    ///
    /// @return     A map of NbtList data with the key being the file name (minus ext)
    /////////////////////////////////////////////////////////////////////////////
    public HashMap<String, NbtList> ReadAllNbtListFromDirectory(String dir, int listType) {
        HashMap<String, NbtList> list = new HashMap<String, NbtList>();
        // dir = "config/keeblarcraft/" + dir;

        File directory = GetFile(dir);

        File[] files = directory.listFiles();

        if (files != null) {
            for (File file : files) {
                String key = file.getName().substring(0, file.getName().length() - 4);
                NbtList nbtList = ReadNbtListFromFile(file, key, listType);

                if (nbtList != null) {
                    // Subtract out '.nbt' from name
                    list.put(key, nbtList);
                    file.delete();
                }
            }
        }

        return list;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ReadNbtListFromFile
    ///
    /// @param[in]  file handler object to read nbt list data from
    ///
    /// @param[in]  key for the nbt compound object
    ///
    /// @param[in]  listType is the NBT Compound list type
    ///
    /// @brief      Reads in NBTList data from a single file
    ///
    /// @return     NbtList data
    /////////////////////////////////////////////////////////////////////////////
    public NbtList ReadNbtListFromFile(File file, String key, int listType) {
        NbtList list = null;

        try {
            NbtCompound c = NbtIo.readCompressed(file);
            list = c.getList(key, listType);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    // WriteToFile
    //
    // Will write or append to file (valid modes: "w" or "a") if file is available. Returns false if not
    public Boolean WriteToFile(String fileName, String data, String mode) {
        Boolean ret = false;

        FileWriter file;
        try {
            file = new FileWriter(GetFile(fileName));
            switch(mode) {
                case "w":
                    file.write(data);
                    ret = true;
                    break;
                case "a":
                    file.append(data);
                    ret = true;
                    break;
                default:
                    Keeblarcraft.LOGGER.debug("Invalid mode to WriteToFile!!");
                    break;
            }

            file.close();
        } catch (IOException e) {
            e.printStackTrace();
            Keeblarcraft.LOGGER.error("Could not open file " + fileName + " to write to it! Possible permissions issue??");
        }

        return ret;
    }


    // WriteToJsonFile
    //
    // Will write to or append to a json file. It will search if the key exists first & update the field;
    // or add a new entry. It should be noted that json objects *can* be buried inside each other. It is
    // considered best (and only) practice to call the "GetJsonStringFromFile" function first from this
    // class and simply iterate to what you would need and then update the entire entry alongside the
    // top-level key.
    //
    // NOTE: THIS DOES NOT SAFE UPDATE THE KEY OBJECT. PRE-EXISTING DATA WILL BE DELETED FOREVER
    public void WriteToJsonFile(String fileName, Object data) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        try {
            FileWriter writer = new FileWriter(GetFile(fileName));
            gson.toJson(data, writer);
            writer.flush();
            writer.close();
        } catch (JsonIOException | IOException e) {
            Keeblarcraft.LOGGER.error("Could not successfully write to json file ["+fileName+"]");
            e.printStackTrace();
        }
    }

    // GetJsonStringFromFile
    //
    // Retrieves json file and converts to desired object. Returns JsonSyntaxException if file could not be fitted to class input
    public <T> T GetJsonObjectFromFile(String fileName, Class<T> classToConvertTo) throws JsonSyntaxException {
        Gson gson = new Gson();
        String ret = "";

        // hot fix: Not sure how to return "false" for invalid conversion when I'm forced to convert or just catch... Look into a better
        // return value in the future - but for now throw JsonSyntaxException no matter what exception is caught
        try {
            File file = GetFile(fileName);
            ret = FileUtils.readFileToString(file, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Caught an exception in retrieving JSON Object from file " + fileName);
            // throw new JsonSyntaxException("");
        }

        return gson.fromJson(ret, classToConvertTo);
    }

    public Boolean DoesFileExist(String fileName) {
        return GetFile(fileName) != null;
    }

    public Boolean DoesDirectoryExist(String dirName) {
        File file = GetFile(dirName);
        return file != null && file.isDirectory();
    }

    public Boolean DeleteDirectory(String dirName) {
        return DeleteFile(dirName);
    }

    // GetFile
    //
    // Returns a file as an arraylist of all the lines in the file. Generally only used for testing
    //
    // NOTE: Returns UTF-8 Encoding of file
    public List<String> GetFileLines(String fileName) {
        List<String> ret = new ArrayList<String>();

        try {
            return Files.readLines(GetFile(fileName), Charsets.UTF_8);
        } catch (IOException e) {
            ret.clear();
        }

        return ret;
    }
}
