import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import edu.stanford.nlp.ling.CoreAnnotations;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.pipeline.Annotation;
import edu.stanford.nlp.pipeline.StanfordCoreNLP;
import edu.stanford.nlp.util.CoreMap;

public class Heuristics {

    public String dataPath;
    public String changedFilesPath;
    public Map<String, String> comments = new HashMap<String, String>();
    public ArrayList<String> bug = new ArrayList<String>();
    public ArrayList<String> feature = new ArrayList<String>();
    public ArrayList<String> test = new ArrayList<String>();
    public ArrayList<String> resource = new ArrayList<String>();
    public ArrayList<String> merge = new ArrayList<String>();
    public ArrayList<String> deprecate = new ArrayList<String>();
    public ArrayList<String> refactoring = new ArrayList<String>();
    public ArrayList<String> lefts = new ArrayList<String>();

    public Formatter formatter;
    public HashMap<String, List<String>> changedFiles;


    public Heuristics(String dataPath, String changedFilesPath) throws IOException {
        super();
        this.dataPath = dataPath;
        this.changedFilesPath=changedFilesPath;
        formatter = new Formatter(changedFilesPath);
        changedFiles = formatter.formatData();
    }

    public void loadData() throws IOException {
        /*
         * expected data is a csv file that contains the following information:
         * PR_url,status,title,mergedCommitID,author_login, commitMessage, author_type,body
         */
        CsvReader reader = new CsvReader(new FileReader(dataPath));
        reader.readHeaders();

        reader.setSafetySwitch(false);
        while (reader.readRecord()){
//            reader.setSafetySwitch(false);
//            reader.readRecord();
            String id = reader.get(4).trim();

            Properties properties = new Properties();
            properties.put("annotators","tokenize, ssplit, pos, lemma");
            StanfordCoreNLP pipeline = new StanfordCoreNLP(properties);
            Annotation annotation;

            if (!id.equals(null)){
                String commit = reader.get(5).trim();
                String lemmatizedComment = commit.replaceAll("[^a-zA-Z0-9 ]", "");
                String commitMessage="";

                annotation = new Annotation(lemmatizedComment);
                pipeline.annotate(annotation);

                List<CoreMap> sentenceList = annotation.get(CoreAnnotations.SentencesAnnotation.class);
                for (CoreMap sentence : sentenceList){
                    for (CoreLabel word : sentence.get(CoreAnnotations.TokensAnnotation.class)){
                        String lemmatizedWord = word.lemma();
                        commitMessage = commitMessage + " " + lemmatizedWord;
                    }
                }


                comments.put(id, commitMessage);
            }
            reader.setSafetySwitch(false);
        }
    }

    public void analysis() {
        for(Map.Entry<String, String> entry: comments.entrySet()) {
            String id = entry.getKey();

            String comment = entry.getValue();
            String c = comments.get(id);

            if(isbug(comment)){
                bug.add(id);
            }
            if(isfeature(comment)){
                feature.add(id);
            }
            if(istest(id, comment)){
                test.add(id);
            }
            if(isdeprecate(comment)){
                deprecate.add(id);
            }
            if(ismerge(comment)){
                merge.add(id);
            }
            if(isresource(id, comment)){
                resource.add(id);
            }
            if(isrefactoring(comment)){
                refactoring.add(id);
            }

        }

        for(String id: comments.keySet()){
            String comment = comments.get(id);
            if(!bug.contains(id) && !feature.contains(id)&& !test.contains(id) &&
                    !deprecate.contains(id) && !merge.contains(id) &&!resource.contains(id) &&!refactoring.contains(id) && comment!="") {
                lefts.add(id);
            }
        }

        for(String id: lefts) {

            String comment = comments.get(id);

            if (isresource_step2(id)) resource.add(id);

            else if(isfeature_step2(comment)) feature.add(id);
        }

        for(String id: feature){
            if(lefts.contains(id)) {
                lefts.remove(id);
            }
        }
        for(String id: resource){
            if(lefts.contains(id)) {
                lefts.remove(id);
            }
        }

    }


    public boolean isbug(String comment) {
        boolean isbug = false;
        Pattern pattern = Pattern.compile("fix|bug|repair|correct|prevent|issue|problem|error|exception|typo|failure", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isbug = true;
        }
        return isbug;
    }

    public boolean isfeature(String comment) {
        boolean isfeature = false;

        Pattern pattern = Pattern.compile("(add).*(feature)", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }

        pattern = Pattern.compile("(add).*(feature)", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }
        pattern = Pattern.compile("(new).*(feature)", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }
        pattern = Pattern.compile("(create).*(new)", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }
        pattern = Pattern.compile("(add).*(new)", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }
        pattern = Pattern.compile("(add).*(missing)", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }

        return isfeature;
    }

    public boolean isfeature_step2(String comment) {
        boolean isfeature = false;

        Pattern pattern = Pattern.compile("enable|add|update|improve|support|new|upgrade|optimize|implement", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isfeature = true;
        }

        return isfeature;
    }

    public boolean istest(String id, String comment) {

        boolean istest = false;
        boolean testFile = false;

        if(this.changedFiles.containsKey(id.trim())) {
            List<String> files = this.changedFiles.get(id.trim());
            for(String file: files) {
                if(file.trim().endsWith("c")||file.trim().endsWith("cpp")||file.trim().endsWith("java")
                        ||file.trim().endsWith("sql")||file.trim().endsWith("js")||file.trim().endsWith("h")||
                        file.trim().endsWith("sources")||file.trim().endsWith("dll")||file.trim().endsWith("cc")||
                        file.trim().endsWith("py")||file.trim().endsWith("jl")) {
                    if(file.toLowerCase().contains("test"))
                        testFile = true;
                }
            }
        }

        Pattern pattern = Pattern.compile("test|testing", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            istest = true;
        }

        return istest || testFile;

    }

    public boolean isdeprecate(String comment) {
        boolean isdeprecate = false;
        boolean morekeys = false;
        Pattern pattern = Pattern.compile("deprecate", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isdeprecate = true;
        }

//        pattern = Pattern.compile("delete |deleted|remove |removed|disable|obsolete|reverse|downgrade|rid", Pattern.CASE_INSENSITIVE);
        pattern = Pattern.compile("delete|remove|disable|obsolete|downgrade|rid", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            morekeys = true;
        }

        return isdeprecate || (morekeys); //&& !bug_fix_match);
    }

    public boolean ismerge(String comment) {
        boolean ismerge = false;
        boolean hasfail= false;
//        Pattern pattern = Pattern.compile("merge |merging|merged |integrate", Pattern.CASE_INSENSITIVE);
        Pattern pattern = Pattern.compile("merge|integrate", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            ismerge = true;
        }

        pattern = Pattern.compile("fail|fix", Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(comment);
        while (matcher.find()) {
            hasfail = true;
        }

        return ismerge && !hasfail;
    }

    public boolean isrefactoring(String comment) {
        boolean isrefactoring = false;
//        Pattern pattern = Pattern.compile("refactor|refactoring|refact|style", Pattern.CASE_INSENSITIVE);
        Pattern pattern = Pattern.compile("refactor|refact|styl", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            isrefactoring = true;
        }

        return isrefactoring;
    }

    public boolean isresource(String id, String comment) {
        boolean keywordsMatching = false;

        Pattern pattern = Pattern.compile("config|license|legal|readme|gitignore|doc", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(comment);
        while (matcher.find()) {
            return true;
        }
        return keywordsMatching;
    }

    private boolean isresource_step2(String id) {

        if(this.changedFiles.containsKey(id.trim())) {
            List<String> files = this.changedFiles.get(id.trim());
            if (!files.isEmpty()){
                for(String file: files) {
                    if(file.trim().endsWith("c")||file.trim().endsWith("cc")||file.trim().endsWith("cpp")||file.trim().endsWith("hpp")||
                            file.trim().endsWith("java")||file.trim().endsWith("js")||file.trim().endsWith("jsp")||
                            file.trim().endsWith("htm")||file.trim().endsWith("html")||file.trim().endsWith("php")||
                            file.trim().endsWith("py")||file.trim().endsWith("rpy")||file.trim().endsWith("css")||
                            file.trim().endsWith("jl")||file.trim().endsWith("rs")) {
                        return false;
                    }
                }
                return true;
            }
        }

        return false;
    }

    public ArrayList<String> get(String type){
        if(type.equals("bug"))
            return this.bug;
        if(type.equals("feature"))
            return this.feature;
        if(type.equals("test"))
            return this.test;
        if(type.equals("merge"))
            return this.merge;
        if(type.equals("deprecate"))
            return this.deprecate;
        if(type.equals("refactoring"))
            return this.refactoring;
        if(type.equals("resource"))
            return this.resource;
        if(type.equals("others"))
            return this.lefts;
        return null;
    }

    private static void getRandomSamples(String project) throws IOException {
        String fileName = "PRclassification_" + project + ".csv";
        String output = project + "_V2RandomSamples4.csv";
        FileWriter writer = new FileWriter(output);

        List<String> lines = Files.readAllLines(Paths.get(fileName), StandardCharsets.UTF_8);

        Random random = new Random(0);

        for (int i=0; i<20; i++){
            writer.write(lines.get(random.nextInt(lines.size())) + '\n');
        }
        writer.close();
    }

    private static void classifyProjectPRs(String project, String PRDataPath, String changedFilesPath, String OutputFileName) throws IOException {
        classifyPRs(project, PRDataPath, changedFilesPath, OutputFileName);
        processMergePRs(OutputFileName);
    }

    private static String classifyPRs(String project, String PRDataPath, String changedFilesPath, String OutputFileName) throws IOException {

        Heuristics heuristics = new Heuristics(PRDataPath,changedFilesPath);
        heuristics.loadData();
        heuristics.analysis();

        List<String> types = Arrays.asList("bug","resource","feature","test","refactoring","merge","deprecate","others");

        HashMap<String,String> categoryPerUrl = new HashMap<>();
        HashMap<String,ArrayList<String>> commitIDsPerCategory = new HashMap<>();
        HashMap<String,ArrayList<String>> PrUrlPerCategory = new HashMap<>();

        for(String type: types) {
            commitIDsPerCategory.put(type,heuristics.get(type));
            PrUrlPerCategory.put(type,new ArrayList<>());
        }

        CsvReader reader = new CsvReader(new FileReader(PRDataPath));
        reader.readHeaders();

        String PRUrl;
        String commitID;
        String category;

        reader.setSafetySwitch(false);
        while (reader.readRecord()){
            PRUrl = reader.get(0).trim();
//            commitID = reader.get(1).trim();
            commitID = reader.get(4).trim();
            category ="";
            for(String type: types) {
                ArrayList<String> listIDs = commitIDsPerCategory.get(type);
                if (listIDs.contains(commitID)){
                    if (category.equals("")) category = type;
                    else{
                        if (!type.equals("others")) category = category + ";" + type;
                    }
                }
            }

            String value = categoryPerUrl.get(PRUrl);
            if (value==null){
                value=category;
            }
            else{
                if (value.contains("others")){
                    value = value.replace("others",category);
                }
//                else if (value.contains("resource")) {
//                    if (!category.equals("others")) value = value.replace("resource",category);
//                }
                else {
                    if (category.contains("others")) category = category.replace("others","");
//                    if (category.contains("resource")) category = category.replace("resource","");
                    value += ";" + category;
                }
            }

            categoryPerUrl.put(PRUrl,value);

            reader.setSafetySwitch(false);
        }

        reader.close();

        CsvWriter writer = new CsvWriter(OutputFileName);
        for (String key:categoryPerUrl.keySet()){
            String value = categoryPerUrl.get(key);
            String[] line = new String[]{key, value};
            writer.writeRecord(line);
        }
        writer.close();
        return OutputFileName;
    }

    private static void processMergePRs(String OutputFileName) throws IOException {
        CsvWriter writer = new CsvWriter(OutputFileName);
        ;
        CsvReader reader = new CsvReader(new FileReader(OutputFileName));

        HashMap<String,String> output = new HashMap<>();

        reader.setSafetySwitch(false);
        while (reader.readRecord()){
            boolean isMerge = true;
            String url = reader.get(0);
            if (!url.equals("")){
                String categories = reader.get(1);
                String value ="";
//                    List<String> lstCategories = List.of(categories.split("-"));
                List<String> lstCategories = Arrays.asList(categories.split(";"));
                for (String categ:lstCategories){
                    if (isMerge){
                        if (categ!=null){
                            if (!categ.contains("merge") && !categ.contains("others")) isMerge = false;
                        }
                    }
                }
                Set<String> distinctCategories = new HashSet<>(lstCategories);
                for (String categ:distinctCategories){
                    if (value.equals("")) value = categ;
                    else value += ";" + categ;
                }
                if (value.contains("merge") && !isMerge){
                    value = value.replaceAll("merge","");
                }

//                    List<String> values = List.of(value.split(";"));
                List<String> values = Arrays.asList(value.split(";"));
                Set<String> distinctValues = new HashSet<>(values);
                value="";
                for (String v:distinctValues){
                    if (!v.equals("")){
                        if (value.equals("")) value = v;
                        else value += ";" + v;
                    }
                }
                if (value.equals("")) value = "others";
                output.put(url,value);
            }
            reader.setSafetySwitch(false);
        }

        for (String key:output.keySet()){
            String value = output.get(key);
            String[] line = new String[]{key, value};
            writer.writeRecord(line);
        }
        writer.close();
    }

    public static void main(String args []) throws Exception {

        String[] projects = new String[]{"Cataclysm-DDA","Julia","Laravel","Node","RPCS3","Rust"};
//        String[] projects = new String[]{"Julia"};

        for (String project : projects){
            System.out.println(project);
            String PRDataPath = project + "_PRmetaV2.csv";
            String changedFilesPath = project + "_ChangedFiles.csv";
            String OutputFileName = "PRclassification_" + project + ".csv";

            classifyProjectPRs(project, PRDataPath, changedFilesPath, OutputFileName);

            getRandomSamples(project);
        }
    }
}
