/*
 * 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.generic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import model.mitre.Technique;
import model.snippet.KeyValuePair;
import model.snippet.Snippet;
import model.snippet.SnippetConstants;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * This class is to be used together with the <code>IClassJsonParser</code>
 * interface for a language specific parser implementation. This class contains
 * functions that are language agnostic and simplify the creation of a parser
 * for a new class.
 *
 * @author Max 'Libra' Kersten [@LibraAnalysis]
 */
public abstract class GenericJsonParser {

    /**
     * Parses the <code>information</code> JSON object into a snippet. Note that
     * this snippet does not have an <code>IClass</code> instance set. This
     * should be done within the language specific parser.
     *
     * @param json the <code>information</code> JSON object
     * @return a snippet with the information fields filled in, without an
     * <code>IClass</code> instance set.
     */
    public Snippet parseSnippetInformation(JSONObject json) {
        //Gets a list of key value pair objects
        List<KeyValuePair> alterators = getAlterators(json.toString());
        //Gets the information-object from the snippet
        json = json.getJSONObject("information");
        //Get the title from the information object
        String title = json.getString("title");
        //Get the description from the information object
        String description = json.getString("description");
        //Get the author from the information object
        String author = json.getString("author");
        //Get the date from the information object
        String date = json.getString("date");
        //Return a new snippet based on this information (excluding the IClass object, since this is language specific)
        return new Snippet(title, description, author, date, alterators);
    }

    /**
     * Parses a JSON array into a set of techniques
     *
     * @param json the JSON array to be parsed
     * @return a set of techniques. Note that this set is possibly empty, if no
     * techniques are present within the JSON array.
     */
    public Set<Technique> getTechniques(JSONArray json) {
        //Creates the set (automatic removal of duplicates) in which all techniques will be stored
        Set<Technique> techniques = new HashSet<>();
        //Iterate through all items within the JSON array
        for (int i = 0; i < json.length(); i++) {
            //Gets the string that is stored at index i
            String technique = json.getString(i);
            //Ignore any empty entries in the array
            if (!technique.isEmpty()) {
                //Add the technique to the set
                //TODO make techniques case insensitive (?)
                techniques.add(Technique.valueOf(technique));
            }
        }
        //Return all techniques
        return techniques;
    }

    /**
     * Gets all arguments from a JSON object.
     *
     * @param json the JSON object to be parsed
     * @return a mapping in which the keys are the variable names and the value
     * is the type or body of the variable (depending on the usage within the
     * JSON object and the language in which it is used). The mapping can be
     * empty if no argument are given.
     */
    public Map<String, String> getArguments(JSONObject json) {
        //Creates the map to store all argumentts in, where the variable name is the key, and the value is the value
        Map<String, String> arguments = new HashMap<>();
        //Iterate through the keyset of the JSON object
        for (String variableName : json.keySet()) {
            //Gets the variable value, based on the varaible name
            String variableValue = json.getString(variableName);
            //Store the combination in the map
            arguments.put(variableName, variableValue);
        }
        //Return the map with all arguments
        return arguments;
    }

    /**
     * Get the strings from a given string. The string matches this regular
     * expression <code>([\s\S]*?)</code> between the string
     * <code>SnippetConstants.ALTERATOR_OPEN</code> and
     * <code>SnippetConstants.ALTERATOR_CLOSE</code>.
     *
     * @param json the string to search through
     * @return the matches, if no matches are found, an empty list is returned
     */
    private List<KeyValuePair> getAlterators(String json) {
        //Creates a list of key value pairs where all matches are stored
        List<KeyValuePair> matches = new ArrayList<>();
        //Use lazy/ungreedy flag to be able to use two or more strings on a single line. Note that the strings need to be between the alterator open and close markings
        Pattern pattern = Pattern.compile(SnippetConstants.ALTERATOR_OPEN + "([\\s\\S]*?)" + SnippetConstants.ALTERATOR_CLOSE);
        //Match the pattern on the given JSON string
        Matcher matcher = pattern.matcher(json);
        //Iterate through all matches
        while (matcher.find()) {
            //Get the match
            String match = matcher.group();
            //Add the match as a new key value pair, where the match is the key and the value is empty
            matches.add(new KeyValuePair(match, ""));
        }
        //Return all key value pairs that are found
        return matches;
    }
}