/*
 * Copyright (C) 2019 Max 'Libra' Kersten [@LibraAnalysis]
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dao;

import dao.generic.IClassJsonParser;
import exception.JsonFolderNotFoundException;
import exception.JsonParseException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import model.language.enums.Language;
import model.snippet.Snippet;
import model.snippet.SnippetConstants;
import org.json.*;
import service.LanguageService;

/**
 * This class loads the snippets from the disk into snippet objects. This class
 * is to be used with the <code>SnippetManager</code>, which is a class with
 * static functions and variables. Therefore, the list of snippets resides in
 * memory and is only resident once within the memory of Genesis. This allows
 * the loading to be done once and only to be done again when an additional
 * snippet is placed within the snippet folder. Regardless of the amount of
 * requests to the list of snippets, only the one within the memory is accessed,
 * resulting in a fast response from Genesis.
 *
 * @author Max 'Libra' Kersten [@LibraAnalysis]
 */
public class SnippetLoader {

    /**
     * The description of the snippets that are loaded (in JSON format) are
     * stored in this list. This way, duplicates are not loaded twice
     */
    private List<String> loadedSnippets;

    /**
     * Create an SnippetLoader object, which can load all snippets that are
     * within the <code>SnippetConstants.SNIPPET_FOLDER</code> folder on the
     * disk.
     */
    public SnippetLoader() {
        loadedSnippets = new ArrayList<>();
    }

    /**
     * Loads a single snippet based on the ID. The file name is equal to the ID
     * of the snippet plus ".json".
     *
     * @param snippetId the id of the snippet to load
     * @return the loaded snippet
     * @throws JsonParseException if the json file cannot be parsed correctly
     * @throws JsonFolderNotFoundException if the json file cannot be found
     */
    protected Snippet loadSnippet(String snippetId) throws JsonParseException, JsonFolderNotFoundException {
        //The folder in which all the JSON files reside (in the root of the file system)
        File jsonFolder = new File(SnippetConstants.SNIPPET_FOLDER);
        //Checks if the folder exists or if the folder is a file
        if (!jsonFolder.exists()) {
            throw new JsonFolderNotFoundException("Unable to load the JSON folder because it does not exist!");
        }
        if (jsonFolder.isFile()) {
            throw new JsonFolderNotFoundException("Unable to load the JSON folder because it is a file!");
        }

        //Listing all files in the folder
        File[] jsonFiles = jsonFolder.listFiles();
        for (File jsonFile : jsonFiles) {
            try {
                //Ignores the ".git" directory and all files that are within it
                if (jsonFile.getAbsolutePath().contains(".git") || !jsonFile.getName().equalsIgnoreCase(snippetId + ".json")) {
                    continue;
                }

                //Load the file from the disk
                JSONObject jsonObject = loadFileFromDisk(jsonFile);
                //Return the parsed value
                return parseSnippet(jsonObject);
            } catch (IOException ex) {
                throw new JsonParseException("Unable to load " + jsonFile.getAbsolutePath() + "!");
            }
        }
        throw new JsonParseException("Unable to find a Snippet file for the given Snippet ID (" + snippetId + ")!");
    }

    /**
     * Loads all snippets that reside within
     * <code>SnippetConstants.SNIPPET_FOLDER</code> folder in the file system.
     * All files are parsed with the corresponding parser and returned within a
     * list.
     *
     * @return a list of all loaded snippets
     * @throws JsonFolderNotFoundException is thrown if the
     * <code>SnippetConstants.SNIPPET_FOLDER</code> folder cannot be found or if
     * the folder is actually a file
     * @throws JsonParseException is thrown if a JSON file cannot be parsed with
     * one of the existing parsers
     */
    protected List<Snippet> loadSnippets() throws JsonFolderNotFoundException, JsonParseException {
        //To avoid duplicates, the variables are instantiated
        List<Snippet> snippets = new ArrayList<>();
        loadedSnippets = new ArrayList<>();
        //The folder in which all the JSON files reside (in the root of the file system)
        File jsonFolder = new File(SnippetConstants.SNIPPET_FOLDER);
        //Checks if the folder exists or if the folder is a file
        if (!jsonFolder.exists()) {
            throw new JsonFolderNotFoundException("Unable to load the JSON folder because it does not exist!");
        }
        if (jsonFolder.isFile()) {
            throw new JsonFolderNotFoundException("Unable to load the JSON folder because it is a file!");
        }

        //Listing all files in the folder
        File[] jsonFiles = jsonFolder.listFiles();
        for (File jsonFile : jsonFiles) {
            //Ignores the ".git" directory and all files that are within it
            if (jsonFile.getAbsolutePath().contains(".git")) {
                continue;
            }
            try {
                //Load the file from the disk
                JSONObject jsonObject = loadFileFromDisk(jsonFile);
                Snippet snippet = parseSnippet(jsonObject);
                //The loaded snippets are checked: if the newly loaded snippet already exists within the list, then it is skipped
                if (!loadedSnippets.contains(snippet.toString())) {
                    //Since the snippet is not loaded yet, it should be added to avoid duplicates of this snippet in the future
                    loadedSnippets.add(snippet.toString());
                    //The final list of all snippets should also contain this snippet
                    snippets.add(snippet);
                }
            } catch (IOException ex) {
                throw new JsonParseException("Unable to load " + jsonFile.getAbsolutePath());
            }
        }
        return snippets;
    }

    /**
     * Parses a JSONObject into a <code>Snippet</code>.
     *
     * @param jsonObject the Snippet object to parse
     * @return the parsed result in a <code>Snippet</code> object
     * @throws JsonParseException is thrown if the JSONObject could not be
     * parsed
     */
    protected Snippet parseSnippet(JSONObject jsonObject) throws JsonParseException {
        //Gets the language field from the class, which is stored as a value from the Language enumeration
        Language language = Language.valueOf(jsonObject.getJSONObject("class").getString("language"));
        //Instantiates the language service to obtain a language specific instance of a class later on
        LanguageService languageService = new LanguageService();
        //Gets the language specific parser that matches with this language
        IClassJsonParser iClassJsonParser = languageService.getJsonParser(language);
        //Returns the parsed result in the form of a Snippet object
        return iClassJsonParser.parse(jsonObject);
    }

    /**
     * Loads a file from the disk and creates a JSON object from it.
     *
     * @param file The file to be read
     * @return the content of the file in the form of a JSON object
     * @throws FileNotFoundException is thrown if the file cannot be found
     * @throws IOException is thrown if something goes wrong when accessing the
     * file
     */
    private JSONObject loadFileFromDisk(File file) throws FileNotFoundException, IOException {
        //Try to open the file using a file reader in a buffered reader to minimise the amount of system calls (and thus load on the system)
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            //Creates a new string builder object
            StringBuilder sb = new StringBuilder();
            //Reads a line from the buffered reader
            String line = br.readLine();

            //As long as the line is not null, it means that there is line
            while (line != null) {
                //The current line is appended
                sb.append(line);
                //A line separator is added based on the operating system
                sb.append(System.lineSeparator());
                //The next line is read
                line = br.readLine();
            }
            //Return the string in the form of a JSON object
            return new JSONObject(sb.toString());
        }
    }
}