package edu.hm.hafner.analysis.parser;

import java.io.IOException;
import java.io.Reader;
import java.util.Locale;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.analysis.ReaderFactory;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Severity;

/**
 * A parser for clair scanner json output.
 * <p>
 * See <a href='https://github.com/arminc/clair-scanner'>clair-scanner</a> for project details.
 *
 * @author Andreas Mandel
 */
public class ClairParser extends IssueParser {
    private static final long serialVersionUID = 42L;

    @Override
    public boolean accepts(final ReaderFactory readerFactory) {
        return readerFactory.getFileName().endsWith(".json");
    }

    @Override
    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
        Report report = new Report();
        try (Reader reader = readerFactory.create()) {
            JSONObject jsonReport = (JSONObject) new JSONTokener(reader).nextValue();
            String image = optStringIgnoreCase(jsonReport,"image");
            JSONArray vulnerabilities = optJsonArrayIgnoreCase(jsonReport, "vulnerabilities");
            for (Object vulnerability : vulnerabilities) {
                if (vulnerability instanceof JSONObject) {
                    report.add(convertToIssue((JSONObject) vulnerability, image));
                }
            }
        } catch (IOException | JSONException | ClassCastException e) {
            throw new ParsingException(e);
        }
        return report;
    }

    private Issue convertToIssue(final JSONObject jsonIssue, String image) {
        StringBuilder message = new StringBuilder();
        appendIfNotEmpty(jsonIssue, message, "featurename", "");
        appendIfNotEmpty(jsonIssue, message, "featureversion", ":");
        appendIfNotEmpty(jsonIssue, message, "description", "");
        appendIfNotEmpty(jsonIssue, message, "fixedby", "Fixed by ");
        appendIfNotEmpty(jsonIssue, message, "link", "see ");
        return new IssueBuilder()
                .setMessage(message.toString())
                .setCategory(optStringIgnoreCase(jsonIssue, "vulnerability"))
                .setSeverity(toSeverity(optStringIgnoreCase(jsonIssue, "severity")))
                .setType(optStringIgnoreCase(jsonIssue, "namespace"))
                .setFileName(image).build();
    }

    private void appendIfNotEmpty(final JSONObject issue, final StringBuilder message, final String key,
            final String head) {
        String text = optStringIgnoreCase(issue, key);
        if (text != null && !text.isEmpty()) {
            if (message.length() > 0 && !head.startsWith(":")) {
                message.append(' ');
            }
            message.append(head).append(text);
        }
    }

    private Severity toSeverity(final String level) {
        switch (String.valueOf(level).toLowerCase(Locale.ENGLISH)) {
            case "defcon1":
                return Severity.ERROR;
            case "critical":
                return Severity.WARNING_HIGH;
            case "high":
                return Severity.WARNING_NORMAL;
            default:
                return Severity.WARNING_LOW;
        }
    }

    private JSONArray optJsonArrayIgnoreCase(JSONObject json, final String searchKey) {
        Object result = optIgnoreCase(json, searchKey);
        return result instanceof JSONArray ? (JSONArray) result : null;
    }

    private String optStringIgnoreCase(JSONObject json, final String searchKey) {
        Object result = optIgnoreCase(json, searchKey);
        return result instanceof String ? (String) result : null;
    }

    private Object optIgnoreCase(JSONObject json, final String searchKey) {
        Object result = json.opt(searchKey);
        if (result == null) {
            for (String key : json.keySet()) {
                if (key.equalsIgnoreCase(searchKey)) {
                    result = json.opt(key);
                    if (result != null) {
                        break;
                    }
                }
            }
        }
        return result;
    }

}

@@ -520,16 +520,65 @@
 everity;
+%0Aimport edu.umd.cs.findbugs.annotations.Nullable;
 %0A%0A/**%0A *
@@ -1110,16 +1110,22 @@
         
+final 
 Report r
@@ -1209,24 +1209,30 @@
             
+final 
 JSONObject j
@@ -1301,24 +1301,30 @@
             
+final 
 String image
@@ -1357,16 +1357,17 @@
 nReport,
+ 
 %22image%22)
@@ -1380,16 +1380,22 @@
         
+final 
 JSONArra
@@ -1916,16 +1916,32 @@
 onIssue,
+ @Nullable final
  String 
@@ -1953,24 +1953,30 @@
 ) %7B%0A        
+final 
 StringBuilde
@@ -2852,16 +2852,22 @@
         
+final 
 String t
@@ -3002,18 +3002,14 @@
 ead.
-startsWith
+equals
 (%22:%22
@@ -3160,16 +3160,26 @@
 everity(
+@Nullable 
 final St
@@ -3596,32 +3596,38 @@
 ArrayIgnoreCase(
+final 
 JSONObject json,
@@ -3653,32 +3653,38 @@
 hKey) %7B%0A        
+final 
 Object result = 
@@ -3778,35 +3778,60 @@
 ) result : n
-ull;%0A    %7D%0A
+ew JSONArray();%0A    %7D%0A%0A    @Nullable
 %0A    private
@@ -3850,32 +3850,38 @@
 tringIgnoreCase(
+final 
 JSONObject json,
@@ -3907,32 +3907,38 @@
 hKey) %7B%0A        
+final 
 Object result = 
@@ -4034,32 +4034,46 @@
  : null;%0A    %7D%0A%0A
+    @Nullable%0A
     private Obje
@@ -4089,16 +4089,22 @@
 oreCase(
+final 
 JSONObje
@@ -4209,32 +4209,253 @@
  == null) %7B%0A    
+        result = searchIgnoreCase(json, searchKey);%0A        %7D%0A        return result;%0A    %7D%0A%0A    @Nullable%0A    private Object searchIgnoreCase(final JSONObject json, final String searchKey) %7B%0A        Object result = null;%0A
         for (Str
@@ -4477,28 +4477,24 @@
 keySet()) %7B%0A
-    
             
@@ -4528,28 +4528,24 @@
 archKey)) %7B%0A
-    
             
@@ -4584,28 +4584,24 @@
             
-    
 if (result !
@@ -4634,41 +4634,15 @@
     
-    
 break;%0A
-                    %7D%0A
     

Check warnings, nullable annotations, finalies